本篇内容是接上篇《Android开发技巧——定制仿微信图片裁剪控件》 的,先简单介绍对上篇所封装的裁剪控件的使用,再详细说明如何使用它进行大图裁剪,包括对旋转图片的裁剪。

裁剪控件的简单使用

XML代码

使用如普通控件一样,首先在布局文件里包含该控件:

 <com.githang.clipimage.ClipImageView
        xmlns:app="http://schemas.android.com/apk/res-auto"android:id="@+id/clip_image_view"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_above="@+id/bottom"app:civClipPadding="@dimen/padding_common"app:civHeight="2"app:civMaskColor="@color/viewfinder_mask"app:civWidth="3"/>

支持的属性如下:

  • civHeight 高度比例,默认为1
  • civWidth 宽度比例,默认为1
  • civTipText 裁剪的提示文字
  • civTipTextSize 裁剪的提示文字的大小
  • civMaskColor 遮罩层颜色
  • civClipPadding 裁剪框边距

Java代码

如果裁剪的图片不大,可以直接设置,就像使用ImageView一样,通过如下四种方法设置图片:

mClipImageView.setImageURI(Uri.fromFile(new File(mInput)));
mClipImageView.setImageBitmap(bitmap);
mClipImageView.setImageResource(R.drawable.xxxx);
mClipImageView.setImageDrawable(drawable);

裁剪的时候调用mClipImageView.clip();就可以返回裁剪之后的Bitmap对象。

大图裁剪

这里会把大图裁剪及图片文件可能旋转的情况一起处理。
注意:由于裁剪图片最终还是需要把裁剪结果以Bitmap对象加载到内存中,所以裁剪之后的图片也是会有大小限制的,否则会有OOM的情况。所以,下面会设一个裁剪后的最大宽度的值。

读取图片旋转角度

在第一篇《 Android开发技巧——Camera拍照功能 》的时候,有提到过像三星的手机,竖屏拍出来的照片还是横的,但是有Exif信息记录了它的旋转方向。考虑到我们进行裁剪的时候,也会遇到类似这样的照片,所以对于这种照片需要旋转的情况,我选择了在裁剪的时候才进行处理。所以首先,我们需要读到图片的旋转角度:

    /*** 读取图片属性:旋转的角度** @param path 图片绝对路径* @return degree旋转的角度*/public static int readPictureDegree(String path) {int degree = 0;try {ExifInterface exifInterface = new ExifInterface(path);int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);switch (orientation) {case ExifInterface.ORIENTATION_ROTATE_90:degree = 90;break;case ExifInterface.ORIENTATION_ROTATE_180:degree = 180;break;case ExifInterface.ORIENTATION_ROTATE_270:degree = 270;break;}} catch (IOException e) {e.printStackTrace();}return degree;}

如果你能确保要裁剪的图片不大不会导致OOM的情况发生的话,是可以直接通过这个角度,创建一个Matrix对象,进行postRotate,然后由原图创建一个新的Bitmap来得到一个正确朝向的图片的。但是这里考虑到我们要裁剪的图片是从手机里读取的,有可能有大图,而我们的裁剪控件本身只实现了简单的手势缩放和裁剪功能,并没有实现大图加载的功能,所以需要在设置图片进行之前进行一些预处理。

采样缩放

由于图片较大,而我们又需要把整张图都加载进来而不是只加载局部,所以就需要在加载的时候进行采样,来加载缩小之后的图片,这样加载到的图片较小,就能有效避免OOM了。
以前文提到的裁剪证件照为例,这里仍以宽度为参考值来计算采样值,具体是用宽还是高或者是综合宽高(这种情况较多,考虑到可能会有很长的图)来计算采样值,还得看你具体情况。在计算采样的时候,我们还需要用到上面读到的旋转值,在图片被旋转90度或180度时,进行宽和高的置换。所以,除了相关的控件,我们需要定义如下相关的变量:

    private String mOutput;private String mInput;private int mMaxWidth;// 图片被旋转的角度private int mDegree;// 大图被设置之前的采样比例private int mSampleSize;private int mSourceWidth;private int mSourceHeight;

计算采样代码如下:

mClipImageView.post(new Runnable() {@Overridepublic void run() {mClipImageView.setMaxOutputWidth(mMaxWidth);mDegree = readPictureDegree(mInput);final boolean isRotate = (mDegree == 90 || mDegree == 270);final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(mInput, options);mSourceWidth = options.outWidth;mSourceHeight = options.outHeight;// 如果图片被旋转,则宽高度置换int w = isRotate ? options.outHeight : options.outWidth;// 裁剪是宽高比例3:2,只考虑宽度情况,这里按border宽度的两倍来计算缩放。mSampleSize = findBestSample(w, mClipImageView.getClipBorder().width());//代码未完,将下面的[缩放及设置]里分段讲到。}
});

由于我们是需要裁剪控件的裁剪框来计算采样,所以需要获取裁剪框,因此我们把上面的代码通过控件的post方法来调用。
inJustDecodeBounds在许多讲大图缩放的博客都有讲到,相信很多朋友都清楚,本文就不赘述了。
注意:采样的值是2的幂次方的,如果你传的值不是2的幂次方,它在计算的时候最终会往下找到最近的2的幂次方的值。所以,如果你后面还需要用这个值来进行计算,就不要使用网上的一些直接用两个值相除进行计算sampleSize的方法。精确的计算方式应该是直接计算时这个2的幂次方的值,例如下面代码:

    /*** 计算最好的采样大小。* @param origin 当前宽度* @param target 限定宽度* @return sampleSize*/private static int findBestSample(int origin, int target) {int sample = 1;for (int out = origin / 2; out > target; out /= 2) {sample *= 2;}return sample;}

缩放及设置

接下来就是设置inJustDecodeBoundsinSampleSize,以及把inPreferredConfig设置为RGB_565,然后把图片给加载进来,如下:

        options.inJustDecodeBounds = false;options.inSampleSize = mSampleSize;options.inPreferredConfig = Bitmap.Config.RGB_565;final Bitmap source = BitmapFactory.decodeFile(mInput, options);

这里加载的图片还是没有旋转到正确朝向的,所以我们要根据上面所计算的角度,对图片进行旋转。我们竖屏拍的图,在一些手机上是横着保存的,但是它会记录一个旋转90度的值在Exif中。如下图中,左边是保存的图,它依然是横着的,右边是我们显示时的图。所以我们读取到这个值后,需要对它进行顺时针的旋转。

代码如下:

        // 解决图片被旋转的问题Bitmap target;if (mDegree == 0) {target = source;} else {final Matrix matrix = new Matrix();matrix.postRotate(mDegree);target = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, false);if (target != source && !source.isRecycled()) {source.recycle();}}mClipImageView.setImageBitmap(target);

这里需要补充的一个注意点是:Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), matrix, false);这个方法返回的Bitmap不一定是重新创建的,如果matrix相同并且宽高相同,而且你没有对Bitmap进行其他设置的话,它可能会返回原来的对象。所以在创建新的Bitmap之后,回收原来的Bitmap时要判断是否可以回收,否则可能导致创建出来的target对象被回收而使ImageView的图片无法显示出来。
如上,就是完整的设置大图时的处理过程的代码。

裁剪

裁剪时需要创建一个裁剪之后的Bitmap,再把它保存下来。下面介绍一下这个创建过程。完整代码如下:

    private Bitmap createClippedBitmap() {if (mSampleSize <= 1) {return mClipImageView.clip();}// 获取缩放位移后的矩阵值final float[] matrixValues = mClipImageView.getClipMatrixValues();final float scale = matrixValues[Matrix.MSCALE_X];final float transX = matrixValues[Matrix.MTRANS_X];final float transY = matrixValues[Matrix.MTRANS_Y];// 获取在显示的图片中裁剪的位置final Rect border = mClipImageView.getClipBorder();final float cropX = ((-transX + border.left) / scale) * mSampleSize;final float cropY = ((-transY + border.top) / scale) * mSampleSize;final float cropWidth = (border.width() / scale) * mSampleSize;final float cropHeight = (border.height() / scale) * mSampleSize;// 获取在旋转之前的裁剪位置final RectF srcRect = new RectF(cropX, cropY, cropX + cropWidth, cropY + cropHeight);final Rect clipRect = getRealRect(srcRect);final BitmapFactory.Options ops = new BitmapFactory.Options();final Matrix outputMatrix = new Matrix();outputMatrix.setRotate(mDegree);// 如果裁剪之后的图片宽高仍然太大,则进行缩小if (mMaxWidth > 0 && cropWidth > mMaxWidth) {ops.inSampleSize = findBestSample((int) cropWidth, mMaxWidth);final float outputScale = mMaxWidth / (cropWidth / ops.inSampleSize);outputMatrix.postScale(outputScale, outputScale);}// 裁剪BitmapRegionDecoder decoder = null;try {decoder = BitmapRegionDecoder.newInstance(mInput, false);final Bitmap source = decoder.decodeRegion(clipRect, ops);recycleImageViewBitmap();return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), outputMatrix, false);} catch (Exception e) {return mClipImageView.clip();} finally {if (decoder != null && !decoder.isRecycled()) {decoder.recycle();}}}

下面分段介绍。

计算在采样缩小前的裁剪框

首先,如果采样值不大于1,也就是我们没有进行图片缩小的时候,就不需要进行下面的计算了,直接调用我们的裁剪控件返回裁剪后的图片即可。否则,就是我们对图片进行缩放的情况了,所以会需要综合我们的采样值mSampleSize,计算我们的裁剪框实际上在原图上的位置。所以会看到相对于上篇所讲的裁剪控件对裁剪框的计算,这里多乘了一个mSampleSize的值,如下:

        // 获取在显示的图片中裁剪的位置final Rect border = mClipImageView.getClipBorder();final float cropX = ((-transX + border.left) / scale) * mSampleSize;final float cropY = ((-transY + border.top) / scale) * mSampleSize;final float cropWidth = (border.width() / scale) * mSampleSize;final float cropHeight = (border.height() / scale) * mSampleSize;

然后我们创建这个在原图大小时的裁剪框:

        final RectF srcRect = new RectF(cropX, cropY, cropX + cropWidth, cropY + cropHeight);

计算在图片旋转前的裁剪框

对于大图的裁剪,我们可以使用BitmapRegionDecoder类,来只加载图片的一部分,也就是用它来加载我们所需要裁剪的那一部分,但是它是从旋转之前的原图进行裁剪的,所以还需要对这个裁剪框进行反向的旋转,来计算它在原图上的位置。
如下图所示,ABCD是旋转90度之后的图片,EFGH是我们的裁剪框。

但是在原图中,它们的对应位置如下图所示:

也就是B点成了A点,A点成了D点,等等。
所以我们获取EFGH在ABCD中的位置,也不能像裁剪控件那样,而需要进行反转之后的计算。以旋转90度为例,现在我们的左上角变成了F点,那么它的left就是原来的top,它的top就是图片的高度减去原来的right,它的right就是原来的bottom,它的bottom就是图片的高度减去原来的left,完整代码如下:

    private Rect getRealRect(RectF srcRect) {switch (mDegree) {case 90:return new Rect((int) srcRect.top, (int) (mSourceHeight - srcRect.right),(int) srcRect.bottom, (int) (mSourceHeight - srcRect.left));case 180:return new Rect((int) (mSourceWidth - srcRect.right), (int) (mSourceHeight - srcRect.bottom),(int) (mSourceWidth - srcRect.left), (int) (mSourceHeight - srcRect.top));case 270:return new Rect((int) (mSourceWidth - srcRect.bottom), (int) srcRect.left,(int) (mSourceWidth - srcRect.top), (int) srcRect.right);default:return new Rect((int) srcRect.left, (int) srcRect.top, (int) srcRect.right, (int) srcRect.bottom);}}

所以在原图上的真正的裁剪框位置是:
final Rect clipRect = getRealRect(srcRect);

局部加载所裁剪的图片部分

大图裁剪,我们使用BitmapRegionDecoder类,它可以只加载指定的某一部分的图片内容,通过它的public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options)方法,我们可以把所裁剪的内容加载出来,得到一个Bitmap,这个Bitmap就是我们要裁剪的内容了。但是,我们加载的这部分内容,同样可能太宽,所以还可能需要进行采样缩小。如下:

    final BitmapFactory.Options ops = new BitmapFactory.Options();final Matrix outputMatrix = new Matrix();//用于最图图片的精确缩放outputMatrix.setRotate(mDegree);// 如果裁剪之后的图片宽高仍然太大,则进行缩小if (mMaxWidth > 0 && cropWidth > mMaxWidth) {ops.inSampleSize = findBestSample((int) cropWidth, mMaxWidth);final float outputScale = mMaxWidth / (cropWidth / ops.inSampleSize);outputMatrix.postScale(outputScale, outputScale);}

计算出采样值sampleSize之后,再使用它及我们计算的裁剪框,加载所裁剪的内容:

        // 裁剪BitmapRegionDecoder decoder = null;try {decoder = BitmapRegionDecoder.newInstance(mInput, false);final Bitmap source = decoder.decodeRegion(clipRect, ops);recycleImageViewBitmap();return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), outputMatrix, false);} catch (Exception e) {return mClipImageView.clip();} finally {if (decoder != null && !decoder.isRecycled()) {decoder.recycle();}}

总结

完整代码见github上我的clip-image项目的示例ClipImageActivity.java。
上面例子中,我所用的图片并不大,下面我打包了一个大图的apk,它使用了维基百科上的一张世界地图,下载地址如下:http://download.csdn.net/detail/maosidiaoxian/9464200

上面的例子截图:

可以看出,在这个例子中,虽然在裁剪过程当中图片被缩放过所以不太清晰,但是我们真正的裁剪是对原图进行裁剪再进行适当的缩放的,所以裁剪之后的图片更清晰。


本文原创,转载请注明CSDN博客出处。
http://blog.csdn.net/maosidiaoxian/article/details/50912577

Android开发技巧——大图裁剪相关推荐

  1. Android开发技巧——自定义控件之自定义属性

    Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...

  2. Android开发技巧——自定义控件之组合控件

    Android开发技巧--自定义控件之组合控件 我准备在接下来一段时间,写一系列有关Android自定义控件的博客,包括如何进行各种自定义,并分享一下我所知道的其中的技巧,注意点等. 还是那句老话,尽 ...

  3. 一些很不错的Android开发技巧

    一些很不错的Android开发技巧,这个项目翻译自 android-tips-tricks 去掉了一些我认为不重要的,对我使用过的东东做了评价,同时翻译了一些自己没有注意到的知识点的文章. ❤️ st ...

  4. android开发技巧杂谈

    android开发技巧一 android的一些常用包是发布在国外的,所以一些包,我们下载不下来,我们可以使用阿里云的镜像地址(maven { url 'https://maven.aliyun.com ...

  5. 社区说|常用 Android 开发技巧

    活动时间 4月7日(本周四) 20:00-21:00 活动日程 20:00-20:45 主题分享 常用 Android 开发技巧 李老师的开发技巧私房菜,一定有你没吃过的菜! 重构技巧 常用插件 阅读 ...

  6. Android开发技巧——定制仿微信图片裁剪控件

    拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动 ...

  7. 移动周刊第 182 期:谈 Android 开发技巧、 iOS 系统框架实践

    写在前面 移动周刊第 182 期如约而至.如果你有好的文章以及优化建议,请发送邮件至mobilehub@csdn.net,在技术探索的道路上我们共同进步. YouTube 推出 VR 视频和 360 ...

  8. Android开发技巧!Android开发大佬的百度,美团,快手等大厂Offer收割之旅,附超全教程文档

    想要成为一名优秀的Android开发,你需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样. 本文参考了目前大部分 Android 应用启动优化的方案,将大家的方案做一个汇总,如果你有这方 ...

  9. Android开发技巧:我的菜单我做主

    本文截选自<Android开发权威指南> Android SDK本身提供了一种默认创建菜单的机制.但通过这种机制创建的菜单虽然从功能上很完备,但在界面效果上实在是有点"土&quo ...

最新文章

  1. 微信小程序-锚点定位+内容滑动控制导航选中
  2. SQL基础---增删查询操作
  3. html焦点试图代码,HTML DOM focus()用法及代码示例
  4. Android --- 微信支付时出现错误:错误的签名,验签失败,return_code=FAIL
  5. movelast对数据记录数有要求吗_新颁布丨药品记录与数据管理要求(试行)解读...
  6. 谷歌浏览器安装Postman插件 亲测有效!!!
  7. springboot改文件头_SpringBoot配置文件常用配置示例
  8. 文档未记录的API之setContentToHTMLString
  9. 数组对象 按某个属性排序
  10. poj - 2255 Tree Recovery
  11. PPT投影仪演示设置
  12. 【Endnote】如何在参考文献前加编号 (1.2.3.等 或 [1] [2] [3]等)
  13. 运行iphone模拟器
  14. 美团面试被问“红黑树”,我一脸懵逼......
  15. 电脑远程开机pcie卡
  16. C语言之字符串(草稿)
  17. 循环卷积和线性卷积的关系
  18. element表格标题两行设置办法
  19. 计算机程序ui设计员工资,ui设计师工资一般多少,发展前景怎么样
  20. Linux进程通信的试验

热门文章

  1. 优酷开放sdk-setOnerroListener
  2. 3GPP Release、TSG、RAN等概念介绍
  3. JAX-WS Web Service
  4. HTML显示xml中的CDATA内容
  5. Docker 获取镜像 - 一
  6. 1-4 开发工具的选择
  7. 第二阶段冲刺第四天(6月3号)
  8. lync2013 错误: 已为不同的传输层安全性(TLS)目标找到类型为“McxInternal”且完全限定的域名(FQDN)为...
  9. 进入前端开发这个领域 ,请问如何进行系统的学习?
  10. javascript 之 面向对象【理解对象】