uCrop使用 github地址 https://github.com/Yalantis/uCrop然後clone或下載到本地,運行之。 效果預覽 app/build.gradle AndroidManifest.xml 這裡theme可以改成自己的 配置uCrop 其他配置 onActivityR ...
uCrop使用
github地址
https://github.com/Yalantis/uCrop
然後clone或下載到本地,運行之。
效果預覽
app/build.gradle
compile 'com.yalantis:ucrop:1.5.0'
AndroidManifest.xml
1 <activity 2 android:name="com.yalantis.ucrop.UCropActivity" 3 android:screenOrientation="portrait" 4 android:theme="@style/Theme.AppCompat.Light.NoActionBar" />
這裡theme可以改成自己的
配置uCrop
1 /** 2 * 啟動裁剪 3 * @param activity 上下文 4 * @param sourceFilePath 需要裁剪圖片的絕對路徑 5 * @param requestCode 比如:UCrop.REQUEST_CROP 6 * @param aspectRatioX 裁剪圖片寬高比 7 * @param aspectRatioY 裁剪圖片寬高比 8 * @return 9 */ 10 public static String startUCrop(Activity activity, String sourceFilePath, 11 int requestCode, float aspectRatioX, float aspectRatioY) { 12 Uri sourceUri = Uri.fromFile(new File(sourceFilePath)); 13 File outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); 14 if (!outDir.exists()) { 15 outDir.mkdirs(); 16 } 17 File outFile = new File(outDir, System.currentTimeMillis() + ".jpg"); 18 //裁剪後圖片的絕對路徑 19 String cameraScalePath = outFile.getAbsolutePath(); 20 Uri destinationUri = Uri.fromFile(outFile); 21 //初始化,第一個參數:需要裁剪的圖片;第二個參數:裁剪後圖片 22 UCrop uCrop = UCrop.of(sourceUri, destinationUri); 23 //初始化UCrop配置 24 UCrop.Options options = new UCrop.Options(); 25 //設置裁剪圖片可操作的手勢 26 options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL); 27 //是否隱藏底部容器,預設顯示 28 options.setHideBottomControls(true); 29 //設置toolbar顏色 30 options.setToolbarColor(ActivityCompat.getColor(activity, R.color.colorPrimary)); 31 //設置狀態欄顏色 32 options.setStatusBarColor(ActivityCompat.getColor(activity, R.color.colorPrimary)); 33 //是否能調整裁剪框 34 options.setFreeStyleCropEnabled(true); 35 //UCrop配置 36 uCrop.withOptions(options); 37 //設置裁剪圖片的寬高比,比如16:9 38 uCrop.withAspectRatio(aspectRatioX, aspectRatioY); 39 //uCrop.useSourceImageAspectRatio(); 40 //跳轉裁剪頁面 41 uCrop.start(activity, requestCode); 42 return cameraScalePath; 43 }
其他配置
1 //設置Toolbar標題 2 void setToolbarTitle(@Nullable String text) 3 //設置裁剪的圖片格式 4 void setCompressionFormat(@NonNull Bitmap.CompressFormat format) 5 //設置裁剪的圖片質量,取值0-100 6 void setCompressionQuality(@IntRange(from = 0) int compressQuality) 7 //設置最多縮放的比例尺 8 void setMaxScaleMultiplier(@FloatRange(from = 1.0, fromInclusive = false) float maxScaleMultiplier) 9 //動畫時間 10 void setImageToCropBoundsAnimDuration(@IntRange(from = 100) int durationMillis) 11 //設置圖片壓縮最大值 12 void setMaxBitmapSize(@IntRange(from = 100) int maxBitmapSize) 13 //是否顯示橢圓裁剪框陰影 14 void setOvalDimmedLayer(boolean isOval) 15 //設置橢圓裁剪框陰影顏色 16 void setDimmedLayerColor(@ColorInt int color) 17 //是否顯示裁剪框 18 void setShowCropFrame(boolean show) 19 //設置裁剪框邊的寬度 20 void setCropFrameStrokeWidth(@IntRange(from = 0) int width) 21 //是否顯示裁剪框網格 22 void setShowCropGrid(boolean show) 23 //設置裁剪框網格顏色 24 void setCropGridColor(@ColorInt int color) 25 //設置裁剪框網格寬 26 void setCropGridStrokeWidth(@IntRange(from = 0) int width)
onActivityResult
經過裁剪,返回結果,這裡我一般只需要裁剪後的圖片絕對路徑(調用上面startUCrop,即返回圖片路徑),然後調介面上傳伺服器。
1 @Override 2 public void onActivityResult(int requestCode, int resultCode, Intent data) { 3 if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) { 4 final Uri resultUri = UCrop.getOutput(data); 5 } else if (resultCode == UCrop.RESULT_ERROR) { 6 final Throwable cropError = UCrop.getError(data); 7 } 8 }
uCrop源碼淺析
uCrop源碼能學習的東西有很多,比如左右滑的標尺,不過我們這裡源碼淺析只關註裁剪部分。
類關係
首先有個大概瞭解:
GestureCropImageView:負責監聽各種手勢
CropImageView:主要完成圖片裁剪工作,和判斷裁剪圖片是否充滿裁剪框
TransformImageView:負責圖片旋轉、縮放、位移操作
入口
由上面的效果圖可知,點擊右上角,調用裁剪操作,代碼如下:
1 @Override 2 public boolean onOptionsItemSelected(MenuItem item) { 3 if (item.getItemId() == R.id.menu_crop) { 4 cropAndSaveImage(); 5 } 6 return super.onOptionsItemSelected(item); 7 } 8 //裁剪和保存圖片 9 protected void cropAndSaveImage() { 10 …… 11 mGestureCropImageView.cropAndSaveImage(mCompressFormat, mCompressQuality, new BitmapCropCallback() { 12 @Override 13 public void onBitmapCropped(@NonNull Uri resultUri) { 14 setResultUri(resultUri, mGestureCropImageView.getTargetAspectRatio()); 15 finish(); 16 } 17 @Override 18 public void onCropFailure(@NonNull Throwable t) { 19 setResultError(t); 20 finish(); 21 } 22 }); 23 }
這裡調用了GestureCropImageView&cropAndSaveImage方法,如下:
1 /** 2 * @param compressFormat 圖片壓縮格式 3 * @param compressQuality 圖片壓縮質量 4 * @param cropCallback 圖片壓縮回調 5 */ 6 public void cropAndSaveImage(@NonNull Bitmap.CompressFormat compressFormat, int compressQuality,@Nullable BitmapCropCallback cropCallback) { 7 //取消所有動畫 8 cancelAllAnimations(); 9 //判斷裁剪圖片是否充滿裁剪框 10 setImageToWrapCropBounds(false); 11 //進行裁剪 12 new BitmapCropTask(getViewBitmap(), mCropRect, RectUtils.trapToRect(mCurrentImageCorners), 13 getCurrentScale(), getCurrentAngle(), 14 mMaxResultImageSizeX, mMaxResultImageSizeY, 15 compressFormat, compressQuality, 16 getImageInputPath(), getImageOutputPath(), 17 cropCallback).execute(); 18 }
裁剪之前
setImageToWrapCropBounds
裁剪之前,先判斷裁剪圖片是否充滿裁剪框,如果沒有,進行移動和縮放讓其充滿。
1 public void setImageToWrapCropBounds(boolean animate) { 2 //mBitmapLaidOut圖片載入OK,isImageWrapCropBounds()檢查圖片是否充滿裁剪框 3 if (mBitmapLaidOut && !isImageWrapCropBounds()) { 4 //當前圖片中心X點 5 float currentX = mCurrentImageCenter[0]; 6 //當前圖片中心Y點 7 float currentY = mCurrentImageCenter[1]; 8 //當前圖片縮放值 9 float currentScale = getCurrentScale(); 10 //差量 11 float deltaX = mCropRect.centerX() - currentX; 12 float deltaY = mCropRect.centerY() - currentY; 13 float deltaScale = 0; 14 //臨時矩陣重置 15 mTempMatrix.reset(); 16 //臨時矩陣移動 17 mTempMatrix.setTranslate(deltaX, deltaY); 18 //複製到新的數組 19 final float[] tempCurrentImageCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length); 20 //將此矩陣應用於二維點的數組,並編寫轉換後的指向數組的點 21 mTempMatrix.mapPoints(tempCurrentImageCorners); 22 //再檢查圖片是否充滿裁剪框 23 boolean willImageWrapCropBoundsAfterTranslate = isImageWrapCropBounds(tempCurrentImageCorners); 24 if (willImageWrapCropBoundsAfterTranslate) { 25 //圖片縮進的數組 26 final float[] imageIndents = calculateImageIndents(); 27 deltaX = -(imageIndents[0] + imageIndents[2]); 28 deltaY = -(imageIndents[1] + imageIndents[3]); 29 } else { 30 RectF tempCropRect = new RectF(mCropRect); 31 mTempMatrix.reset(); 32 mTempMatrix.setRotate(getCurrentAngle()); 33 mTempMatrix.mapRect(tempCropRect); 34 //獲取裁剪圖片的邊 35 final float[] currentImageSides = RectUtils.getRectSidesFromCorners(mCurrentImageCorners); 36 deltaScale = Math.max(tempCropRect.width() / currentImageSides[0], 37 tempCropRect.height() / currentImageSides[1]); 38 deltaScale = deltaScale * currentScale - currentScale; 39 } 40 if (animate) { 41 //移動或縮放圖片(有動畫效果) 42 post(mWrapCropBoundsRunnable = new WrapCropBoundsRunnable( 43 CropImageView.this, mImageToWrapCropBoundsAnimDuration, currentX, currentY, deltaX, deltaY, 44 currentScale, deltaScale, willImageWrapCropBoundsAfterTranslate)); 45 } else { 46 //移動圖片 47 postTranslate(deltaX, deltaY); 48 if (!willImageWrapCropBoundsAfterTranslate) { 49 //縮放圖片 50 zoomInImage(currentScale + deltaScale, mCropRect.centerX(), mCropRect.centerY()); 51 } 52 } 53 } 54 }
進行裁剪
裁剪放到了非同步,即BitmapCropTask繼承AsyncTask,先設置原始圖片resizeScale值,然後通過ExifInterface保存新的圖片,即裁剪後的圖片
1 public class BitmapCropTask extends AsyncTask<Void, Void, Throwable> { 2 3 …… 4 /** 5 * @param viewBitmap 裁剪圖片bitmap 6 * @param cropRect 裁剪矩形 7 * @param currentImageRect 當前圖片矩形 8 * @param currentScale 當前圖片縮放值 9 * @param currentAngle 當前圖片角度 10 * @param maxResultImageSizeX 圖片裁剪後最大寬值 11 * @param maxResultImageSizeY 圖片裁剪後最大高值 12 * @param compressFormat 圖片裁剪的格式 13 * @param compressQuality 圖片裁剪的質量 14 * @param imageInputPath 裁剪圖片路徑 15 * @param imageOutputPath 圖片裁剪後路徑 16 * @param cropCallback 裁剪回調 17 */ 18 public BitmapCropTask(@Nullable Bitmap viewBitmap, 19 @NonNull RectF cropRect, @NonNull RectF currentImageRect, 20 float currentScale, float currentAngle, 21 int maxResultImageSizeX, int maxResultImageSizeY, 22 @NonNull Bitmap.CompressFormat compressFormat, int compressQuality, 23 @NonNull String imageInputPath, @NonNull String imageOutputPath, 24 @Nullable BitmapCropCallback cropCallback) { 25 …… 26 } 27 @Override 28 @Nullable 29 protected Throwable doInBackground(Void... params) { 30 if (mViewBitmap == null || mViewBitmap.isRecycled()) { 31 return new NullPointerException("ViewBitmap is null or already recycled"); 32 } 33 if (mCurrentImageRect.isEmpty()) { 34 return new NullPointerException("CurrentImageRect is empty"); 35 } 36 //設置resizeScale值 37 float resizeScale = resize(); 38 try { 39 //裁剪 40 crop(resizeScale); 41 //回收 42 mViewBitmap.recycle(); 43 mViewBitmap = null; 44 } catch (Throwable throwable) { 45 return throwable; 46 } 47 return null; 48 } 49 private float resize() { 50 //初始Options 51 final BitmapFactory.Options options = new BitmapFactory.Options(); 52 //查詢該點陣圖,而無需分配存儲器,可獲取outHeight(圖片原始高度)和 outWidth(圖片的原始寬度) 53 options.inJustDecodeBounds = true; 54 //裁剪圖片解碼 55 BitmapFactory.decodeFile(mImageInputPath, options); 56 //原始圖片和裁剪後圖片比值 57 float scaleX = options.outWidth / mViewBitmap.getWidth(); 58 float scaleY = options.outHeight / mViewBitmap.getHeight(); 59 float resizeScale = Math.min(scaleX, scaleY); 60 mCurrentScale /= resizeScale; 61 //初始化值為1 62 resizeScale = 1; 63 if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0) { 64 float cropWidth = mCropRect.width() / mCurrentScale; 65 float cropHeight = mCropRect.height() / mCurrentScale; 66 if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) { 67 scaleX = mMaxResultImageSizeX / cropWidth; 68 scaleY = mMaxResultImageSizeY / cropHeight; 69 //設置resizeScale,如果是2就是高度和寬度都是原始的一半 70 resizeScale = Math.min(scaleX, scaleY); 71 mCurrentScale /= resizeScale; 72 } 73 } 74 return resizeScale; 75 } 76 private boolean crop(float resizeScale) throws IOException { 77 //ExifInterface這個介面提供了圖片文件的旋轉,gps,時間等信息,從原始圖片讀出Exif標簽 78 ExifInterface originalExif = new ExifInterface(mImageInputPath); 79 int top = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale); 80 int left = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale); 81 int width = Math.round(mCropRect.width() / mCurrentScale); 82 int height = Math.round(mCropRect.height() / mCurrentScale); 83 //複製圖片 84 boolean cropped = cropCImg(mImageInputPath, mImageOutputPath, 85 left, top, width, height, mCurrentAngle, resizeScale, 86 mCompressFormat.ordinal(), mCompressQuality); 87 if (cropped) { 88 //拿到裁剪後圖片 89 copyExif(originalExif, width, height); 90 } 91 return cropped; 92 } 93 @SuppressWarnings("JniMissingFunction") 94 native public boolean cropCImg(String inputPath, String outputPath, 95 int left, int top, int width, int height, float angle, float resizeScale, 96 int format, int quality) throws IOException, OutOfMemoryError; 97 /** 98 * @param originalExif 原始圖片Exif 99 * @param width 裁剪後圖片寬 100 * @param height 裁剪後圖片高 101 * @throws IOException 是否異常 102 */ 103 public void copyExif(ExifInterface originalExif, int width, int height) throws IOException { 104 //Exif標簽數組 105 String[] attributes = new String[]{ 106 ExifInterface.TAG_APERTURE, 107 …… 108 }; 109 //指定裁剪後圖片路徑,初始化新的ExifInterface 110 ExifInterface newExif = new ExifInterface(mImageOutputPath); 111 String value; 112 for (String attribute : attributes) { 113 value = originalExif.getAttribute(attribute); 114 if (!TextUtils.isEmpty(value)) { 115 //設置Exif標簽 116 newExif.setAttribute(attribute, value); 117 } 118 } 119 newExif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, String.valueOf(width)); 120 newExif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH, String.valueOf(height)); 121 newExif.setAttribute(ExifInterface.TAG_ORIENTATION, "0"); 122 //保存 123 newExif.saveAttributes(); 124 } 125 @Override 126 protected void onPostExecute(@Nullable Throwable t) { 127 if (mCropCallback != null) { 128 if (t == null) { 129 //介面回調,over 130 mCropCallback.onBitmapCropped(Uri.fromFile(new File(mImageOutputPath))); 131 } else { 132 mCropCallback.onCropFailure(t); 133 } 134 } 135 } 136 }
總結
uCrop功能強大,對於我來說,有很多東西值得學習,難點如Rect包含問題(其實這塊還不是很理解),新知識如ExifInterface操作圖片,BitmapFactory顯示圖片的知識點溫故等,還有自定義左右滑的標尺,都是不錯的學習源碼。拋磚引玉至此,over。