共计 18918 个字符,预计需要花费 48 分钟才能阅读完成。
本人能力不足,在看到源码最后一部分的时候大量抄袭可能是最详细的UCrop源码解析
1. uCrop简介
uCrop是目前较火的图片裁剪框架,开发者宣称他会比目前市面上所有的图片裁剪方案都要更流畅。外加他封装程度较高,可自定义,而且颜值很高(似乎这个才是重点),现在越来越多APP选择使用它。github
2. 使用方法
得益于uCrop优秀的封装,uCrop的使用方法特简单。
2.1 导入依赖
先在项目的build.gradle中添加
1 2 3 4 5 6
allprojects { repositories { jcenter() maven { url "https://jitpack.io" } } }
并在module的build.gradle中添加
implementation 'com.github.yalantis:ucrop:2.2.3' – 轻量级框架
implementation 'com.github.yalantis:ucrop:2.2.3-native' – 获得框架全部强大的功能以及图片的高质量(最终可能会导致apk的大小增加1.5MB以上)
由于框架的本质是调用到另一个Activity去处理图片,所以需要在AndroidManifest.xml中将UCropActivity添加进去
1 2 3 4
<activity android:name ="com.yalantis.ucrop.UCropActivity" android:screenOrientation ="portrait" android:theme ="@style/Theme.AppCompat.Light.NoActionBar" />
到这你就能把cUrop全部导入到你的项目里面了,接下来咱们就拉将如何调用
2.2 开始基本的调用
调用起来很简单:
1 2
UCrop.of(sourceUri, destinationUri) .start(context);
其中sourceUri是输入图片的Uri,destinationUri是输出图片的Uri。然后他就会由Intent的调动跳到UCropActivity,用户就在UCropActivity里面进行图片裁剪操作,然后最后由UCropActivity发起一个Intent回到你的Activity。
2.3 处理回来的数据
由于是从UCropAcitivity传回数据,所以你需要在你的Activity里面的onActivityResult方法处理uCrop返回的信息:
1 2 3 4 5 6 7 8
@Override public void onActivityResult (int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) { final Uri resultUri = UCrop.getOutput(data); } else if (resultCode == UCrop.RESULT_ERROR) { final Throwable cropError = UCrop.getError(data); } }
到这,基本用法就完了,你就可以尽情的使用uCrop。但是我前面说过,uCrop封装程度好,这点很多图片处理框架都可以做到,基本上都是把需要的数据传到自己的Activity之后由自己的Activity处理,所以很多框架看起来都有优秀的封装,那uCrop相比其他又有啥好呢,答案就是自定义灵活:
2.4 uCrop高阶用法
2.4.1 配置uCrop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
public static String startUCrop (Activity activity, String sourceFilePath, int requestCode, float aspectRatioX, float aspectRatioY) { Uri sourceUri = Uri.fromFile(new File (sourceFilePath)); File outDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); if (!outDir.exists()) { outDir.mkdirs(); } File outFile = new File (outDir, System.currentTimeMillis() + ".jpg" ); String cameraScalePath = outFile.getAbsolutePath(); Uri destinationUri = Uri.fromFile(outFile); UCrop uCrop = UCrop.of(sourceUri, destinationUri); UCrop.Options options = new UCrop .Options(); options.setAllowedGestures(UCropActivity.SCALE, UCropActivity.ROTATE, UCropActivity.ALL); options.setHideBottomControls(true ); options.setToolbarColor(ActivityCompat.getColor(activity, R.color.colorPrimary)); options.setStatusBarColor(ActivityCompat.getColor(activity, R.color.colorPrimary)); options.setFreeStyleCropEnabled(true ); uCrop.withOptions(options); uCrop.withAspectRatio(aspectRatioX, aspectRatioY); uCrop.start(activity, requestCode); return cameraScalePath; }
2.4.2 其他配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
void setToolbarTitle (@Nullable String text) void setCompressionFormat (@NonNull Bitmap.CompressFormat format) void setCompressionQuality (@IntRange(from = 0) int compressQuality) void setMaxScaleMultiplier (@FloatRange(from = 1.0, fromInclusive = false) float maxScaleMultiplier) void setImageToCropBoundsAnimDuration (@IntRange(from = 100) int durationMillis) void setMaxBitmapSize (@IntRange(from = 100) int maxBitmapSize) void setOvalDimmedLayer (boolean isOval) void setDimmedLayerColor (@ColorInt int color) void setShowCropFrame (boolean show) void setCropFrameStrokeWidth (@IntRange(from = 0) int width) void setShowCropGrid (boolean show) void setCropGridColor (@ColorInt int color) void setCropGridStrokeWidth (@IntRange(from = 0) int width)
3. 源码解析
在我开始说源码之前,我建议大家可以先看下我下面的连接,因为本框架的作者真的是个好人,他不仅为我们贡献了这么好的一个框架,还把自己写这个框架的思路都写了出来,大家可以看看英文原版 国内网友翻译版 百度网页翻译机翻版 其实我个人感觉百度机翻没有谷歌翻译的好,大家有条件的可以使用谷歌翻译浏览器插件翻译整个网页(谷歌翻译好像国内可以直接访问)
代码结构大致分为三个部分:
3.1 第一部分:UCropActivity(整个框架的外在,用户操作图片的地方)
他的功能就是项目主要的界面,以及实现一些基本的初始化。你跳转到uCrop看到的那个操作图片的界面就是它。
这块看源码的时候代码居多,但是,说实话,就像刚刚说的一样,他除了初始化还是初始化。初始化完Toolbar接着初始化ViewGroup,初始化完ViewGroup接着初始化Image数据等等。所以这块我就没咋细看(其实是因为代码太长了,逃)
3.2 第二部分:OverlayView(绘制裁剪框)
这一块主要就是来画你所看到的图片中的裁剪的辅助线。
在构造方法里面就调用了一个方法,就是init(),而init()方法也就干了一件事——判断。当系统小于JELLY_BEA_MR2也就是Android4.3时,启动了硬件加速,至于为什么setLayerType(LAYER_TYPE_SOFTWARE, null);这个看着就像启动硬件加速的方法,甚至参数里面还有软件这个单词的方法能启动硬件加速,请大家移步HenCoder Android 自定义 View 1-8 硬件加速 (进去直接搜索这个方法即可,就能找到解释的地方),我再次不做解释。
这个类主要有两个方法
drawDimmedLayer()绘制裁剪框之外的灰色部分
drawCropGrid()绘制裁剪框
那我们分别来看下这两个方法:
3.2.1 drawDimmedLayer()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
protected void drawDimmedLayer (@NonNull Canvas canvas) { canvas.save(); if (mCircleDimmedLayer) { canvas.clipPath(mCircularPath, Region.Op.DIFFERENCE); } else { canvas.clipRect(mCropViewRect, Region.Op.DIFFERENCE); } canvas.drawColor(mDimmedColor); canvas.restore(); if (mCircleDimmedLayer) { canvas.drawCircle(mCropViewRect.centerX(), mCropViewRect.centerY(), Math.min(mCropViewRect.width(), mCropViewRect.height()) / 2.f , mDimmedStrokePaint); } }
首先就是一个mCircleDimmedLayer,这个我真的很迷,因为我不知道她是咋来的,于是我就看OverlayView有没有对这个变量的赋值,于是整个类我就找到了一个setCircleDimmedLayer()方法,于是我看这个方法是在哪被调用了的,然后我就找到他分别被UCropActivity和UCropFragment两个类调用到,而且一个是intent.getBooleanExtra()方法一个是bundle.getBoolean()方法,看到这个我相信大家都有点数了,这明显就是其他类传过来的啊,我发现他两的key的值都是UCrop.Options.EXTRA_CIRCLE_DIMMED_LAYER,那我就懂了,找整个框架里面哪儿提到过这个值不就得了,于是我就发现除了上面两个方法以及他的初始化以外,我发现了第4个调用的地方,也是唯一一个调用的地方——Ucrop.setCircleDimmedLayer():
1 2 3 4 5 6 7
public void setCircleDimmedLayer (boolean isCircle) { mOptionBundle.putBoolean(EXTRA_CIRCLE_DIMMED_LAYER, isCircle); }
注释上面是原话,下面是我百度机翻的翻译。看了就懂了吧,反正我没懂,我也完全没有见到哪调用过这个方法,我更不懂啥叫希望暗显层有个圆,啥玩意?充满线条的黑??? 直到我将UCrop的调用方法修改了并运行之后我才懂了:
1 2 3 4 5
val options = UCrop.Options()options.setCircleDimmedLayer(true ) UCrop.of(uri, destinationUri) .withOptions(options) .start(this )
结果是:
然后就懂了,应该是能截一个圆形的图案吧,然后我点下了✔️,然后……
无话可说,作者牛逼!!!
回去回去,刚刚说到drawDimmedLayer(),可以看到,如果mCircleDimmedLayer为true就调用clipPath()跟着路径裁切一个矩形加原,不然的话就调用clipRect()裁切一个矩形。然后加入颜色,然后完了
3.2.2 drawCropGrid()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
protected void drawCropGrid (@NonNull Canvas canvas) { if (mShowCropGrid) { if (mGridPoints == null && !mCropViewRect.isEmpty()) { mGridPoints = new float [(mCropGridRowCount) * 4 + (mCropGridColumnCount) * 4 ]; int index = 0 ; for (int i = 0 ; i < mCropGridRowCount; i++) { mGridPoints[index++] = mCropViewRect.left; mGridPoints[index++] = (mCropViewRect.height() * (((float ) i + 1.0f ) / (float ) (mCropGridRowCount + 1 ))) + mCropViewRect.top; mGridPoints[index++] = mCropViewRect.right; mGridPoints[index++] = (mCropViewRect.height() * (((float ) i + 1.0f ) / (float ) (mCropGridRowCount + 1 ))) + mCropViewRect.top; } for (int i = 0 ; i < mCropGridColumnCount; i++) { mGridPoints[index++] = (mCropViewRect.width() * (((float ) i + 1.0f ) / (float ) (mCropGridColumnCount + 1 ))) + mCropViewRect.left; mGridPoints[index++] = mCropViewRect.top; mGridPoints[index++] = (mCropViewRect.width() * (((float ) i + 1.0f ) / (float ) (mCropGridColumnCount + 1 ))) + mCropViewRect.left; mGridPoints[index++] = mCropViewRect.bottom; } } if (mGridPoints != null ) { canvas.drawLines(mGridPoints, mCropGridPaint); } } if (mShowCropFrame) { canvas.drawRect(mCropViewRect, mCropFramePaint); } if (mFreestyleCropMode != FREESTYLE_CROP_MODE_DISABLE) { canvas.save(); mTempRect.set(mCropViewRect); mTempRect.inset(mCropRectCornerTouchAreaLineLength, -mCropRectCornerTouchAreaLineLength); canvas.clipRect(mTempRect, Region.Op.DIFFERENCE); mTempRect.set(mCropViewRect); mTempRect.inset(-mCropRectCornerTouchAreaLineLength, mCropRectCornerTouchAreaLineLength); canvas.clipRect(mTempRect, Region.Op.DIFFERENCE); canvas.drawRect(mCropViewRect, mCropFrameCornersPaint); canvas.restore(); } }
一开头又是一个和上面类似的变量mShowCropGrid,这下我就不说我找的具体步骤,他的功能就是如果他是true就会在裁剪框中显示9宫格线,为false就没有。接着就是画线部分,我觉得这个我不用讲啥,也没啥讲的,唯一就是为什么mGridPoints这个数组的大小是4的倍数,大家可以看下这个博客Android Canvas DrawLines中第一个参数的解释
3.3 第三部分:GestureCropImageView(正在框架的内在,代码操作操作图片的地方)
这个是整个项目最核心的地方。前面的两部分都是UI的,而这个才是真正的对图片进行处理的部分,也是我最想知道了解的部分。 这部分作者也在他的博客里面说的最多最清楚。 作者把这部分的逻辑分为了三个部分
TransformImageView extends ImageView 他处理了
从源拿到图片
将图片进变换(平移、缩放、旋转),并应用到当前图片上
CropImageView extends TransformImageView 他处理了
绘制裁剪边框和网格
为裁剪区域设置一张图片(如果用户对图片操作导致裁剪区域出现了空白,那么图片应自动移动到边界填充空白区域)
继承父类方法,使用更精准的规则来操作矩阵(限制最大和最小缩放比)
添加方法和缩小的方法
裁剪图片
GestureCropImageView extends CropImageView 他处理了
监听用户手势,并调用对应的正确的方法
作者说这是最容易的部分。 在看这个类之前我们先来看看BitmapLoadTask类,这个类是一切图像处理的基础,这个类负责了Uri解码bitmap,并处理分辨率: 首先根据拿到的Uri解析位图:
1 2 3 4 5 6 7 8 9 10 11 12 13
final ParcelFileDescriptor parcelFileDescriptor;try { parcelFileDescriptor = mContext.getContentResolver().openFileDescriptor(mInputUri, "r" ); } catch (FileNotFoundException e) { return new BitmapWorkerResult (e); } final FileDescriptor fileDescriptor;if (parcelFileDescriptor != null ) { fileDescriptor = parcelFileDescriptor.getFileDescriptor(); } else { return new BitmapWorkerResult (new NullPointerException ("ParcelFileDescriptor was null for given Uri: [" + mInputUri + "]" )); }
现在,可以使用BitmapFactory方法解码FileDescriptor。
但在解码位图之前,有必要知道它的大小,因为如果分辨率太高,位图将被二次采样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
final BitmapFactory.Options options = new BitmapFactory .Options();options.inJustDecodeBounds = true ; BitmapFactory.decodeFileDescriptor(fileDescriptor, null , options); options.inSampleSize = calculateInSampleSize(options, requiredWidth, requiredHeight); options.inJustDecodeBounds = false ; Bitmap decodeSampledBitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null , options);close(parcelFileDescriptor); ExifInterface exif = getExif(uri);if (exif != null ) { int exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); return rotateBitmap(decodeSampledBitmap, exifToDegrees(exifOrientation)); } else { return decodeSampledBitmap; }
这样就拿到了bitmap实例了,就可以去TansformImageView去对图片进行调整了。 其实这个类我也不知道说啥😂,我觉得这个类也就是把Matrix的postTranslate()、postRotate()和postScale()方法给封装了下。 关于Matrix的知识大家可以参考这篇博客:安卓自定义View进阶-Matrix原理
3.3.2 CropImageView
这一层是最复杂的一层,作者的操作大致可以分为3步:图片裁剪框偏移计算、图片归为动画处理、裁剪图片
第一步:图片裁剪框偏移计算 当用户手指移开时,要确保图片处于裁剪区域中,如果不处于,需要通过平移把它移过来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
public void setImageToWrapCropBounds (boolean animate) { if (mBitmapLaidOut && !isImageWrapCropBounds()) { float currentX = mCurrentImageCenter[0 ]; float currentY = mCurrentImageCenter[1 ]; float currentScale = getCurrentScale(); float deltaX = mCropRect.centerX() - currentX; float deltaY = mCropRect.centerY() - currentY; float deltaScale = 0 ; mTempMatrix.reset(); mTempMatrix.setTranslate(deltaX, deltaY); final float [] tempCurrentImageCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length); mTempMatrix.mapPoints(tempCurrentImageCorners); boolean willImageWrapCropBoundsAfterTranslate = isImageWrapCropBounds(tempCurrentImageCorners); if (willImageWrapCropBoundsAfterTranslate) { final float [] imageIndents = calculateImageIndents(); deltaX = -(imageIndents[0 ] + imageIndents[2 ]); deltaY = -(imageIndents[1 ] + imageIndents[3 ]); } else { RectF tempCropRect = new RectF (mCropRect); mTempMatrix.reset(); mTempMatrix.setRotate(getCurrentAngle()); mTempMatrix.mapRect(tempCropRect); final float [] currentImageSides = RectUtils.getRectSidesFromCorners(mCurrentImageCorners); deltaScale = Math.max(tempCropRect.width() / currentImageSides[0 ], tempCropRect.height() / currentImageSides[1 ]); deltaScale = deltaScale * currentScale - currentScale; } if (animate) { post(mWrapCropBoundsRunnable = new WrapCropBoundsRunnable ( CropImageView.this , mImageToWrapCropBoundsAnimDuration, currentX, currentY, deltaX, deltaY, currentScale, deltaScale, willImageWrapCropBoundsAfterTranslate)); } else { postTranslate(deltaX, deltaY); if (!willImageWrapCropBoundsAfterTranslate) { zoomInImage(currentScale + deltaScale, mCropRect.centerX(), mCropRect.centerY()); } } } }
第二步:处理平移 通过一个Runnable线程来处理平移,并且通过时间差值的计算来移动动画,使动画看起来更真实:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
private static class WrapCropBoundsRunnable implements Runnable { private final WeakReference<CropImageView> mCropImageView; private final long mDurationMs, mStartTime; private final float mOldX, mOldY; private final float mCenterDiffX, mCenterDiffY; private final float mOldScale; private final float mDeltaScale; private final boolean mWillBeImageInBoundsAfterTranslate; public WrapCropBoundsRunnable (CropImageView cropImageView, long durationMs, float oldX, float oldY, float centerDiffX, float centerDiffY, float oldScale, float deltaScale, boolean willBeImageInBoundsAfterTranslate) { mCropImageView = new WeakReference <>(cropImageView); mDurationMs = durationMs; mStartTime = System.currentTimeMillis(); mOldX = oldX; mOldY = oldY; mCenterDiffX = centerDiffX; mCenterDiffY = centerDiffY; mOldScale = oldScale; mDeltaScale = deltaScale; mWillBeImageInBoundsAfterTranslate = willBeImageInBoundsAfterTranslate; } @Override public void run () { CropImageView cropImageView = mCropImageView.get(); if (cropImageView == null ) { return ; } long now = System.currentTimeMillis(); float currentMs = Math.min(mDurationMs, now - mStartTime); float newX = CubicEasing.easeOut(currentMs, 0 , mCenterDiffX, mDurationMs); float newY = CubicEasing.easeOut(currentMs, 0 , mCenterDiffY, mDurationMs); float newScale = CubicEasing.easeInOut(currentMs, 0 , mDeltaScale, mDurationMs); if (currentMs < mDurationMs) { cropImageView.postTranslate(newX - (cropImageView.mCurrentImageCenter[0 ] - mOldX), newY - (cropImageView.mCurrentImageCenter[1 ] - mOldY)); if (!mWillBeImageInBoundsAfterTranslate) { cropImageView.zoomInImage(mOldScale + newScale, cropImageView.mCropRect.centerX(), cropImageView.mCropRect.centerY()); } if (!cropImageView.isImageWrapCropBounds()) { cropImageView.post(this ); } } } }
下面还有另一个线程,用于双击放大:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
private static class ZoomImageToPosition implements Runnable { private final WeakReference<CropImageView> mCropImageView; private final long mDurationMs, mStartTime; private final float mOldScale; private final float mDeltaScale; private final float mDestX; private final float mDestY; public ZoomImageToPosition (CropImageView cropImageView, long durationMs, float oldScale, float deltaScale, float destX, float destY) { mCropImageView = new WeakReference <>(cropImageView); mStartTime = System.currentTimeMillis(); mDurationMs = durationMs; mOldScale = oldScale; mDeltaScale = deltaScale; mDestX = destX; mDestY = destY; } @Override public void run () { CropImageView cropImageView = mCropImageView.get(); if (cropImageView == null ) { return ; } long now = System.currentTimeMillis(); float currentMs = Math.min(mDurationMs, now - mStartTime); float newScale = CubicEasing.easeInOut(currentMs, 0 , mDeltaScale, mDurationMs); if (currentMs < mDurationMs) { cropImageView.zoomInImage(mOldScale + newScale, mDestX, mDestY); cropImageView.post(this ); } else { cropImageView.setImageToWrapCropBounds(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
public void cropAndSaveImage (@NonNull Bitmap.CompressFormat compressFormat, int compressQuality, @Nullable BitmapCropCallback cropCallback) { cancelAllAnimations(); setImageToWrapCropBounds(false ); final ImageState imageState = new ImageState ( mCropRect, RectUtils.trapToRect(mCurrentImageCorners), getCurrentScale(), getCurrentAngle()); final CropParameters cropParameters = new CropParameters ( mMaxResultImageSizeX, mMaxResultImageSizeY, compressFormat, compressQuality, getImageInputPath(), getImageOutputPath(), getExifInfo()); new BitmapCropTask (getViewBitmap(), imageState, cropParameters, cropCallback).execute(); }
这块核心方法还是在BitmapCropTask中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
private float resize () { final BitmapFactory.Options options = new BitmapFactory .Options(); options.inJustDecodeBounds = true ; BitmapFactory.decodeFile(mImageInputPath, options); boolean swapSides = mExifInfo.getExifDegrees() == 90 || mExifInfo.getExifDegrees() == 270 ; float scaleX = (swapSides ? options.outHeight : options.outWidth) / (float ) mViewBitmap.getWidth(); float scaleY = (swapSides ? options.outWidth : options.outHeight) / (float ) mViewBitmap.getHeight(); float resizeScale = Math.min(scaleX, scaleY); mCurrentScale /= resizeScale; resizeScale = 1 ; if (mMaxResultImageSizeX > 0 && mMaxResultImageSizeY > 0 ) { float cropWidth = mCropRect.width() / mCurrentScale; float cropHeight = mCropRect.height() / mCurrentScale; if (cropWidth > mMaxResultImageSizeX || cropHeight > mMaxResultImageSizeY) { scaleX = mMaxResultImageSizeX / cropWidth; scaleY = mMaxResultImageSizeY / cropHeight; resizeScale = Math.min(scaleX, scaleY); mCurrentScale /= resizeScale; } } return resizeScale; } private boolean crop (float resizeScale) throws IOException { ExifInterface originalExif = new ExifInterface (mImageInputPath); cropOffsetX = Math.round((mCropRect.left - mCurrentImageRect.left) / mCurrentScale); cropOffsetY = Math.round((mCropRect.top - mCurrentImageRect.top) / mCurrentScale); mCroppedImageWidth = Math.round(mCropRect.width() / mCurrentScale); mCroppedImageHeight = Math.round(mCropRect.height() / mCurrentScale); boolean shouldCrop = shouldCrop(mCroppedImageWidth, mCroppedImageHeight); Log.i(TAG, "Should crop: " + shouldCrop); if (shouldCrop) { boolean cropped = cropCImg(mImageInputPath, mImageOutputPath, cropOffsetX, cropOffsetY, mCroppedImageWidth, mCroppedImageHeight, mCurrentAngle, resizeScale, mCompressFormat.ordinal(), mCompressQuality, mExifInfo.getExifDegrees(), mExifInfo.getExifTranslation()); if (cropped && mCompressFormat.equals(Bitmap.CompressFormat.JPEG)) { ImageHeaderParser.copyExif(originalExif, mCroppedImageWidth, mCroppedImageHeight, mImageOutputPath); } return cropped; } else { FileUtils.copyFile(mImageInputPath, mImageOutputPath); return false ; } }
3.3.3 GestureCropImageView
这个类主要就是对手势的监听,所以我们简单粗暴,直接找他的onTouchEvent方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
@Override public boolean onTouchEvent (MotionEvent event) { if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) { cancelAllAnimations(); } if (event.getPointerCount() > 1 ) { mMidPntX = (event.getX(0 ) + event.getX(1 )) / 2 ; mMidPntY = (event.getY(0 ) + event.getY(1 )) / 2 ; } mGestureDetector.onTouchEvent(event); if (mIsScaleEnabled) { mScaleDetector.onTouchEvent(event); } if (mIsRotateEnabled) { mRotateDetector.onTouchEvent(event); } if ((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) { setImageToWrapCropBounds(); } return true ; }