Android實(shí)現(xiàn)長(zhǎng)按圖片保存至相冊(cè)功能
前言:前面寫了一篇reactnative的學(xué)習(xí)筆記,說(shuō)reactnative的Android框架中有很多福利,確實(shí)是的,也說(shuō)到了我們app中的一個(gè)把圖片保存到相冊(cè)的功能,好吧,還是準(zhǔn)備寫一篇博客,就當(dāng)筆記了~
先上幾張app的圖片:
一進(jìn)app就是一個(gè)進(jìn)度條加載圖片(我待會(huì)也會(huì)說(shuō)一下進(jìn)度條view跟怎么監(jiān)聽(tīng)圖片加載過(guò)程):
圖片加載完畢后:
長(zhǎng)按圖片進(jìn)入相冊(cè)可以看到我們保存的圖片:
監(jiān)聽(tīng)圖片加載的loaddingview源碼(不是很難,我就直接貼代碼了):
package com.leo.camerroll; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.util.TypedValue; import android.view.View; import android.view.animation.Animation; import android.view.animation.LinearInterpolator; import android.widget.ProgressBar; /** * Created by leo on 17/1/22. */ public class LoadingView extends ProgressBar { private final int DEFAULT_RADIUS = dp2px(15); private final int DEFAULT_REACH_COLOR = 0XFFFFFFFF; private final int DEFAULT_UNREACH_COLOR = 0X88000000; private final long ANIM_DURATION = 1000; private final String BASE_TEXT = "00%"; private boolean isStop; private int mRadius = DEFAULT_RADIUS; private int mStrokeWidth; private Paint reachPaint; private Paint unreachPaint; private Paint textPaint; private Paint bgPaint; private int mStartAngle=0; private float mSweepAngle=360*0.382f; private ValueAnimator anim; public LoadingView(Context context) { this(context, null); } public LoadingView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } private void initView() { reachPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); reachPaint.setStrokeCap(Paint.Cap.ROUND); reachPaint.setStyle(Paint.Style.STROKE); unreachPaint = new Paint(reachPaint); reachPaint.setColor(DEFAULT_REACH_COLOR); unreachPaint.setColor(DEFAULT_UNREACH_COLOR); textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); textPaint.setStyle(Paint.Style.STROKE); textPaint.setColor(Color.WHITE); textPaint.setFakeBoldText(true); bgPaint=new Paint(Paint.ANTI_ALIAS_FLAG|Paint.DITHER_FLAG); bgPaint.setStrokeCap(Paint.Cap.ROUND); bgPaint.setColor(Color.argb(44,0,0,0)); setMax(100); } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int defWidth = resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec); int defHeight = resolveSize(getSuggestedMinimumHeight(), heightMeasureSpec); int expectSize = Math.min(defHeight, defWidth); if (expectSize <= 0) { expectSize = mRadius * 2; } else { mRadius = expectSize / 2; } mStrokeWidth = mRadius / 5; reachPaint.setStrokeWidth(mStrokeWidth); unreachPaint.setStrokeWidth(mStrokeWidth); setMeasuredDimension(expectSize, expectSize); float textSize=0; while(true){ textSize+=0.1; textPaint.setTextSize(textSize); if(textPaint.measureText(BASE_TEXT,0,BASE_TEXT.length())>=mRadius){ break; } } } @Override protected synchronized void onDraw(Canvas canvas) { if(isStop){ setVisibility(View.GONE); return; } //drawbackground transparent canvas.drawCircle(getWidth()/2,getWidth()/2,mRadius-mStrokeWidth,bgPaint); //draw reach drawProgressReach(canvas); //draw progress text drawProgressText(canvas); } private void drawProgressText(Canvas canvas) { String text=String.valueOf((int)(getProgress()*1.0f/getMax()*100))+"%"; int centerX=getWidth()/2; int centerY=getWidth()/2; int baseX= (int) (centerX-textPaint.measureText(text,0,text.length())/2); int baseY= (int) (centerY-(textPaint.getFontMetrics().ascent+textPaint.getFontMetrics().descent)/2); canvas.drawText(text,baseX,baseY,textPaint); } private void drawProgressReach(Canvas canvas) { canvas.drawArc(new RectF(0 + mStrokeWidth / 2, 0 + mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2), mStartAngle, mSweepAngle, false, reachPaint); //drawonreach canvas.drawArc(new RectF(0 + mStrokeWidth / 2, 0 + mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2, mRadius * 2 - mStrokeWidth / 2), mStartAngle+mSweepAngle, 360-mSweepAngle,false, unreachPaint); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if(anim==null){ anim=ValueAnimator.ofInt(0,360); anim.setInterpolator(new LinearInterpolator()); anim.setDuration(ANIM_DURATION); anim.setRepeatCount(Animation.INFINITE); anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { if(animation!=null&&animation.getAnimatedValue()!=null){ int startAngle= (int) animation.getAnimatedValue(); mStartAngle=startAngle; postInvalidate(); } } }); }else{ anim.cancel(); anim.removeAllUpdateListeners(); } anim.start(); } public void loadCompleted() { isStop=true; if(anim!=null){ anim.cancel(); anim.removeAllUpdateListeners(); this.setVisibility(View.GONE); } } /** * @param size * @return px */ private int dp2px(int size) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, getContext().getResources().getDisplayMetrics()); } }
實(shí)現(xiàn)起來(lái)還是很簡(jiǎn)單的,就是動(dòng)態(tài)改變兩端弧的起點(diǎn)和終點(diǎn),通過(guò)屬性動(dòng)畫不斷的在(0-360)循環(huán),代碼應(yīng)該還算比較清晰哈!~~~~
圖片加載用了一個(gè)AsyncTask:
private class DownImageTask extends AsyncTask<String, Long, Bitmap> { private ImageView imageView; private long contentLength; public DownImageTask(ImageView imageView) { this.imageView = imageView; } @Override protected Bitmap doInBackground(String... params) { Bitmap bitmap = null; BufferedInputStream bis = null; ByteArrayOutputStream bos = null; try { File fileDir=new File(getApplication().getExternalCacheDir(),"images"); if(fileDir==null||!fileDir.isDirectory()){ fileDir.mkdir(); } File file=new File(fileDir.getAbsolutePath()+"/"+params[0].hashCode()+".png"); if(file!=null&&file.length()>0){ return bitmap=BitmapFactory.decodeFile(file.getAbsolutePath()); } bos=new ByteArrayOutputStream(); byte[] buffer = new byte[512]; long total=0; int len ; URL url = new URL(params[0]); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); this.contentLength = conn.getContentLength(); bis = new BufferedInputStream(conn.getInputStream()); while ((len = bis.read(buffer)) != -1) { total+=len; publishProgress(total); Thread.sleep(100); bos.write(buffer, 0, len); bos.flush(); } bitmap= BitmapFactory.decodeByteArray(bos.toByteArray(),0,bos.toByteArray().length); saveBitmapToDisk(bos,params[0]); } catch (Exception e) { e.printStackTrace(); } finally { try { if (bis != null) { bis.close(); } if (bos != null) { bos.close(); } } catch (IOException e) { e.printStackTrace(); } } return bitmap; } private void saveBitmapToDisk(final ByteArrayOutputStream baos, final String url) { new Thread(){ @Override public void run() {BufferedOutputStream bos=null; try{ if(!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){ Log.e("TAG","內(nèi)存卡不存在"); return; } Log.e("TAG","開(kāi)始保存圖片至內(nèi)存卡~~"); byte[] bytes = baos.toByteArray(); File fileDir=new File(getApplication().getExternalCacheDir(),"images"); if(fileDir==null||!fileDir.isDirectory()){ fileDir.mkdir(); } File file=new File(fileDir.getAbsolutePath()+"/"+url.hashCode()+".png"); file.createNewFile(); bos=new BufferedOutputStream(new FileOutputStream(file)); bos.write(bytes); bos.flush(); Log.e("TAG","圖片已經(jīng)保存至內(nèi)存卡~~"); }catch (Exception e){ e.printStackTrace(); }finally { if(bos!=null){ try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }.start(); } @Override protected void onProgressUpdate(Long... values) { mLoadingView.setProgress((int) ((values[0].longValue() * 1.0f / contentLength) * 100)); } @Override protected void onPostExecute(Bitmap bitmap) { if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); mLoadingView.loadCompleted(); } } }
加載完畢后把圖片存放在了內(nèi)存卡中(當(dāng)然,這是我隨便寫的一個(gè)圖片加載,大家換成自己的加載框架哈,):
private void saveBitmapToDisk(final ByteArrayOutputStream baos, final String url) { new Thread(){ @Override public void run() {BufferedOutputStream bos=null; try{ if(!Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){ Log.e("TAG","內(nèi)存卡不存在"); return; } Log.e("TAG","開(kāi)始保存圖片至內(nèi)存卡~~"); byte[] bytes = baos.toByteArray(); File fileDir=new File(getApplication().getExternalCacheDir(),"images"); if(fileDir==null||!fileDir.isDirectory()){ fileDir.mkdir(); } File file=new File(fileDir.getAbsolutePath()+"/"+url.hashCode()+".png"); file.createNewFile(); bos=new BufferedOutputStream(new FileOutputStream(file)); bos.write(bytes); bos.flush(); Log.e("TAG","圖片已經(jīng)保存至內(nèi)存卡~~"); }catch (Exception e){ e.printStackTrace(); }finally { if(bos!=null){ try { bos.close(); } catch (IOException e) { e.printStackTrace(); } } } } }.start(); }
這里我們是需要把圖片保存到內(nèi)存卡中,所以考慮到了android 6.0的運(yùn)行時(shí)權(quán)限,所以小伙伴們也一定要判斷哦,我在oncreate的時(shí)候就判斷了:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestAlertWindowPermission(); } }
private static final int REQUEST_CODE = 1; private void requestAlertWindowPermission() { ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},REQUEST_CODE); }
下面就是講長(zhǎng)按圖片保存至相冊(cè)了:
mImageView.setOnLongClickListener(new View.OnLongClickListener(){ @Override public boolean onLongClick(View v) { if(mImageView.getDrawable() instanceof BitmapDrawable){ Toast.makeText(getApplicationContext(),"長(zhǎng)按保存圖片至相冊(cè)",Toast.LENGTH_SHORT).show(); File fileDir=new File(getApplication().getExternalCacheDir(),"images"); File file=new File(fileDir.getAbsolutePath()+"/"+IMAGE_URL.hashCode()+".png"); if(file!=null&&file.length()>0){ CameraRollManager rollManager=new CameraRollManager(MainActivity.this, Uri.parse(file.getAbsolutePath())); rollManager.execute(); } } return false; } });
CameraRollManager是我直接copy的reactnatvie中的android模塊的代碼:
CameraRollManager.java
package com.leo.camerroll.camera; import android.content.Context; import android.content.Intent; import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.widget.Toast; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.channels.FileChannel; /** * Created by leo on 17/1/22. */ public class CameraRollManager extends GuardedAsyncTask{ private static Context mContext; private final Uri mUri; private static Handler handler=new Handler(Looper.getMainLooper()){ @Override public void handleMessage(Message msg) { Toast.makeText(mContext,"保存成功!",Toast.LENGTH_SHORT).show(); Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); mContext.startActivity(intent); } }; public CameraRollManager(Context context, Uri uri) { super(context); mContext = context; mUri = uri; } @Override protected void doInBackgroundGuarded(Object[] params) { File source = new File(mUri.getPath()); FileChannel input = null, output = null; try { File exportDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); exportDir.mkdirs(); if (!exportDir.isDirectory()) { return; } File dest = new File(exportDir, source.getName()); int n = 0; String fullSourceName = source.getName(); String sourceName, sourceExt; if (fullSourceName.indexOf('.') >= 0) { sourceName = fullSourceName.substring(0, fullSourceName.lastIndexOf('.')); sourceExt = fullSourceName.substring(fullSourceName.lastIndexOf('.')); } else { sourceName = fullSourceName; sourceExt = ""; } while (!dest.createNewFile()) { dest = new File(exportDir, sourceName + "_" + (n++) + sourceExt); } input = new FileInputStream(source).getChannel(); output = new FileOutputStream(dest).getChannel(); output.transferFrom(input, 0, input.size()); input.close(); output.close(); MediaScannerConnection.scanFile( mContext, new String[]{dest.getAbsolutePath()}, null, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { handler.sendEmptyMessage(0); } }); } catch (IOException e) { } finally { if (input != null && input.isOpen()) { try { input.close(); } catch (IOException e) { } } if (output != null && output.isOpen()) { try { output.close(); } catch (IOException e) { } } } } }
GuardedAsyncTask.java:
package com.leo.camerroll.camera; import android.content.Context; import android.os.AsyncTask; /** * Created by leo on 17/1/22. */ public abstract class GuardedAsyncTask <Params, Progress> extends AsyncTask<Params, Progress, Void> { private final Context mReactContext; protected GuardedAsyncTask(Context reactContext) { mReactContext = reactContext; } @Override protected final Void doInBackground(Params... params) { try { doInBackgroundGuarded(params); } catch (RuntimeException e) { } return null; } protected abstract void doInBackgroundGuarded(Params... params); }
好啦?。。?看著簡(jiǎn)單哈,花了我一個(gè)上午的時(shí)間,還是自己不熟練的原因額,感覺(jué)高了一段時(shí)間rn,結(jié)果android原生又生疏了,小伙伴們?nèi)绻蚕裎乙粯拥脑挘欢ㄒ>毩?xí)哦,兩個(gè)東西都是需要常敲的那種,不然又忘記了?。?!
最后附上demo的git鏈接:
https://github.com/913453448/CamerRoll
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android圖片添加水印圖片并把圖片保存到文件存儲(chǔ)的實(shí)現(xiàn)代碼
- Android人臉識(shí)別Demo豎屏YUV方向調(diào)整和圖片保存(分享)
- Android長(zhǎng)按imageview把圖片保存到本地的實(shí)例代碼
- android中Glide實(shí)現(xiàn)加載圖片保存至本地并加載回調(diào)監(jiān)聽(tīng)
- Android 實(shí)現(xiàn)WebView點(diǎn)擊圖片查看大圖列表及圖片保存功能
- Android 自定義View手寫簽名并保存圖片功能
- Android使用webView長(zhǎng)按保存下載網(wǎng)絡(luò)圖片
- Android保存多張圖片到本地的實(shí)現(xiàn)方法
- Android開(kāi)發(fā)實(shí)現(xiàn)的保存圖片到相冊(cè)功能示例
- Android開(kāi)發(fā)實(shí)現(xiàn)保存圖片到手機(jī)相冊(cè)功能
- Android 圖片保存到相冊(cè)不顯示的解決方案(兼容Android 10及更高版本)
相關(guān)文章
Android Studio下添加assets目錄的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇Android Studio下添加assets目錄的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03Android實(shí)現(xiàn)精確到天時(shí)分秒的搶購(gòu)倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)精確到天時(shí)分秒的搶購(gòu)倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02Android PopWindow 設(shè)置背景亮度的實(shí)例
這篇文章主要介紹了Android PopWindow 設(shè)置背景亮度的實(shí)例的相關(guān)資料,這里提供實(shí)現(xiàn)方法,希望能幫助有所需要的朋友,需要的朋友可以參考下2017-08-08Kotlin 協(xié)程 supervisorScope {} 運(yùn)行崩潰解決方法
看過(guò)很多?supervisorScope {}?文檔的使用,我照抄一摸一樣的代碼,運(yùn)行就崩潰,最后找到了解決方法,應(yīng)該是kotlin版本更新做過(guò)改動(dòng),當(dāng)前我使用的是?androidx.core:core-ktx:1.9.0,本文給大家介紹Kotlin 協(xié)程 supervisorScope {} 運(yùn)行崩潰解決方法,感興趣的朋友一起看看吧2024-01-01Kotlin語(yǔ)言中CompileSdkVersion與targetSdkVersion的區(qū)別淺析
這篇文章主要介紹了Kotlin語(yǔ)言中CompileSdkVersion和targetSdkVersion有什么區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02Android?通用視頻組件開(kāi)發(fā)過(guò)程詳解
這篇文章主要介紹了Android?通用視頻組件開(kāi)發(fā)的詳細(xì)過(guò)程,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07Android開(kāi)發(fā)-之環(huán)境的搭建(圖文詳解)
這篇文章主要介紹了Android開(kāi)發(fā)-之環(huán)境的搭建(圖文詳解),具有一定的參考價(jià)值,有興趣的可以了解一下。2016-11-11