在很多App 中,需要注册登录,那么就免不了 设置用户的头像。头像无非就是方形 或者 圆形,那么就诞生了这样一个需求:

  • 从相册中选择一张图片
  • 中间区域是圆形 或者 方形的透明裁剪框
  • 裁剪框周围是阴影
  • 图片可以移动、缩放

网上有很多,包括Github上,但是绝大多数都是 移动裁剪框,而不是移动图片。但是最后还是找到了一个可以参考模仿的例子《Android开发技巧——定制仿微信图片裁剪控件》。这篇博客详细讲述了自定义裁剪控件的过程。以前我也是直接拿过来用的。直到某一天,产品跑过来说:“裁剪中间这个矩形框能不能改成圆的,因为我们的头像是圆的”。 于是我就开始改这个代码了,说不上优化,只是换一种思路去解决问题。

有图有真相

          

原作者定制内容

  • 合并裁剪框的内容到ImageView中
  • 裁剪框可以是任意长宽比的矩形
  • 裁剪框的左右外边距可以设置
  • 遮罩层颜色可以设置
  • 裁剪框下有提示文字
  • 后面产品又加入了一条裁剪图片的最大大小

我的修改:

  • 去掉了底部提示文字(有需要可以自己加
  • 画遮罩和中间的裁剪框是 用到了 PorterDuffXfermode 方法
  • 在最后裁剪成头像时 也用到了上述方法
  • 圆形框或者矩形框 可以自由选择
  • 图片的边框可以滑倒裁剪框内部,松开手指时,会有一个回弹效果

自定义属相修改

 <attr name="civHeight" format="integer" /><attr name="civWidth" format="integer" /><attr name="civMaskColor" format="color" /><attr name="civClipPadding" format="dimension" /><attr name="civClipCircle" format="boolean" />
  1. civHegiht 和 civWidth 是中间矩形裁剪框的比值
  2. civMaskColor  是裁剪框外围的颜色,也成遮罩颜色
  3. civPadding  是裁剪框距离我们控件的边距

其他内容像 参数变量,构造方法等等,原作者都做了详细的描述,我这里就不再一一赘述了,接下来我们就重点讲讲绘制裁剪框和遮罩图片拖动双击缩放 以及绘制头像

绘制裁剪框

基本思路:在一个新建的 Canvas 上画一个 遮罩(bitmap),再用一个透明的画笔 设置 PorterDuffXfermode 为PorterDuff.Mode.CLEAR,在遮罩上画一个 矩形或圆形,这样一个中间为空的裁剪框 的遮罩就形成了。将遮罩画在所选图片上之上,别忘了 还要画一个 圆形框或者矩形框

/*** 画中间的边框(方形或是圆形)*/public void drawRectangleOrCircle(Canvas canvas) {float cx = mClipBorder.left + mClipBorder.width() / 2f;float cy = mClipBorder.top + mClipBorder.height() / 2f;float radius = mClipBorder.height() / 2f;RectF rectF = new RectF(mClipBorder.left, mClipBorder.top, mClipBorder.right, mClipBorder.bottom);Bitmap bitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);Canvas temp = new Canvas(bitmap);Paint transparentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);PorterDuffXfermode porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);transparentPaint.setColor(Color.TRANSPARENT);temp.drawRect(0, 0, temp.getWidth(), temp.getHeight(), mPaint);transparentPaint.setXfermode(porterDuffXfermode);if (mDrawCircleFlag) { // 画圆temp.drawCircle(cx, cy, radius, transparentPaint);} else { // 画矩形(可以设置矩形的圆角)temp.drawRect(rectF, transparentPaint);}canvas.drawBitmap(bitmap, 0, 0, null);mPaint.setStyle(Paint.Style.STROKE);mPaint.setColor(Color.WHITE);mPaint.setStrokeWidth(DeviceUtil.dip2px(2));if (mDrawCircleFlag) {canvas.drawCircle(cx, cy, radius, mPaint);} else {canvas.drawRect(rectF, mPaint);}}

底部的遮罩层是目标图像,上面的裁剪形状是源图像,PorterDuff.Mode.CLEAR 起到了清空源图像所在区域图像的作用。

双击缩放

说到双击,我们必要要检测到手指检测,当然啦,我们不会傻到自己写一塌逻辑去检测用户是否是双击了屏幕。这里我们用到了这个GestureDetector来监听用户手势,如果是双击,自然会走双击的回掉方法 public boolean onDoubleTap(MotionEvent e)

手势监听

setScaleType(ScaleType.MATRIX);mGestureDetector = new GestureDetector(context,new SimpleOnGestureListener() {@Overridepublic boolean onDoubleTap(MotionEvent e) {if (isAutoScale)return true;float x = e.getX();float y = e.getY();if (getScale() < mScaleMin) { //如果当前的缩放倍数小于一开始适配裁剪框缩放倍数的两倍ClipImageView.this.postDelayed(new AutoScaleRunnable(mScaleMin, x, y), 16);} else {ClipImageView.this.postDelayed(new AutoScaleRunnable(mInitScale, x, y), 16);}isAutoScale = true;return true;}});

大家有没有注意到 这个 setScaleType(ScaleType.MATRIX) ,该方法就是设置 imageview 可以根据 矩阵去缩放。

缩放操作

 private class AutoScaleRunnable implements Runnable {static final float BIGGER = 1.07f;static final float SMALLER = 0.93f;private final float mTargetScale;private final float tmpScale;/*** 缩放的中心*/private final float x;private final float y;/*** 传入目标缩放值,根据目标值与当前值,判断应该放大还是缩小*/AutoScaleRunnable(float targetScale, float x, float y) {this.mTargetScale = targetScale;this.x = x;this.y = y;if (getScale() < mTargetScale) {tmpScale = BIGGER;} else {tmpScale = SMALLER;}}@Overridepublic void run() {// 进行缩放mScaleMatrix.postScale(tmpScale, tmpScale, x, y);checkBorder();setImageMatrix(mScaleMatrix);final float currentScale = getScale();// 如果值在合法范围内,继续缩放if (((tmpScale > 1f) && (currentScale < mTargetScale))|| ((tmpScale < 1f) && (mTargetScale < currentScale))) {ClipImageView.this.postDelayed(this, 16);} else {// 设置为目标的缩放比例final float deltaScale = mTargetScale / currentScale;mScaleMatrix.postScale(deltaScale, deltaScale, x, y);checkBorder();setImageMatrix(mScaleMatrix);isAutoScale = false;}}}

这边是根据 放大倍数(1.07) 和缩小倍数(0.93) 去缩放的,是慢慢缩放,而不是一下子缩放,这样会很突兀。注意缩放完了以后记得去检查边界,因为在缩放后,图片的边界在裁剪框里面,所以需要再去移动图片 以达到适配裁剪框的目的

两个手指缩放

通过多指操作 来检测缩放,我们用到了这个监听类 ScaleGestureDetector

 @Overridepublic boolean onScale(ScaleGestureDetector detector) {float scale = getScale();float scaleFactor = detector.getScaleFactor();if (getDrawable() == null)return true;//缩放的范围控制if ((scale < mScaleMax && scaleFactor > 1.0f)|| (scale > mInitScale && scaleFactor < 1.0f)) {//缩放阙值最小值判断if (scaleFactor * scale < mInitScale) {scaleFactor = mInitScale / scale;}if (scaleFactor * scale > mScaleMax) {scaleFactor = mScaleMax / scale;}//设置缩放比例mScaleMatrix.postScale(scaleFactor, scaleFactor,detector.getFocusX(), detector.getFocusY());checkBorder();setImageMatrix(mScaleMatrix);}return true;}

我们在控制缩放的时候,还是需要计算一下在不在我们的缩放倍数范围里。

移动图片

这边的设计是:

  • 如果图片的宽 或 高正好等于裁剪框的宽 或高,那么久移动不了图片
  • 放大后,移动图片,图片的边界可以移动到裁剪框里面,手指松开后,图片自动校正适配裁剪框
 @Overridepublic boolean onTouch(View v, MotionEvent event) {if (mGestureDetector.onTouchEvent(event))return true;mScaleGestureDetector.onTouchEvent(event);float x = 0, y = 0;// 拿到触摸点的个数final int pointerCount = event.getPointerCount();// 得到多个触摸点的x与y均值for (int i = 0; i < pointerCount; i++) {x += event.getX(i);y += event.getY(i);}x /= pointerCount;y /= pointerCount;//每当触摸点发生变化时,重置mLasX , mLastYif (pointerCount != lastPointerCount) {isCanDrag = false;mLastX = x;mLastY = y;}lastPointerCount = pointerCount;switch (event.getAction()) {case MotionEvent.ACTION_MOVE:float dx = x - mLastX;float dy = y - mLastY;if (!isCanDrag) {isCanDrag = isCanDrag(dx, dy);}if (isCanDrag) {if (getDrawable() != null) {RectF rectF = getMatrixRectF();// 如果宽度小于屏幕宽度,则禁止左右移动if ((int) rectF.width() <= mClipBorder.width()) {dx = 0;}// 如果高度小于屏幕高度,则禁止上下移动if ((int) rectF.height() <= mClipBorder.height()) {dy = 0;}// 这里主要是 当宽或者高 大于 裁剪框的高或宽时,移动到与裁剪框边重合时,可以继续移动if (rectF.left > mClipBorder.left + 1 || rectF.top > mClipBorder.top + 1 || rectF.right < mClipBorder.right - 1 || rectF.bottom < mClipBorder.bottom - 1) {dx = dx * 0.25f;dy = dy * 0.25f;}mScaleMatrix.postTranslate(dx, dy);setImageMatrix(mScaleMatrix);}}mLastX = x;mLastY = y;break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:lastPointerCount = 0;// 当抬起手指时,如果划过了,没有填满裁剪框,就要自动弹回checkBorder();setImageMatrix(mScaleMatrix);break;}return true;}

裁剪头像

原著作者的思路:在矩阵中获取 图片的 缩放倍数,移动距离,进行相除和相加减,最后在原图上进行裁剪,这样有一个不好的地方,float 和 int 相互转换,会有1 px 的误差,而这个误差很有可能在裁剪时 崩溃,因为超出了图片的宽度,这是在实践中验证过了。

作为新时代的青年,我们当然要换一种想法,那就是 PorterDuffXfermode,只不过这里的model 是PorterDuff.Mode.SRC_IN。看上去还不错哦,与开头画裁剪框和遮罩 形成了 首尾呼应。

先在 新建的Canvas 上 画一个 透明的 Bitmap,中间画一个 填充型矩形,设置画笔 的Xfermode ,接着将变化后的bitmap 画上去,这样两个Bitmap 一合成,就在画布上形成了 周围是透明,只有中间矩形里 是图片,最后将矩形图片截图出来。

/*** 截图操作*/public Bitmap clip() {final Drawable drawable = getDrawable();final Bitmap originalBitmap = ((BitmapDrawable) drawable).getBitmap();Bitmap bottomBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_4444);Canvas canvas = new Canvas(bottomBitmap);Paint paint = new Paint();paint.setAntiAlias(true);canvas.drawRect(mClipBorder, paint);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));canvas.drawBitmap(originalBitmap, mScaleMatrix, paint);return Bitmap.createBitmap(bottomBitmap, (int) mClipBorder.left, (int) mClipBorder.top, (int) mClipBorder.width(), (int) mClipBorder.height());}

效果演示

截图控件

Android 仿微信 头像裁剪

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

Android 仿微信裁剪图片相关推荐

  1. Android仿微信聊天记录“图片及视频”默认最新图片从底部显示(时间排序升序)

    Android仿微信聊天记录"图片及视频"默认最新图片从底部显示(时间排序升序) 1.设置recycler的LinearLayoutManager LinearLayoutMana ...

  2. android仿微信的图片选择器

    PictureSelector 项目地址: arvinljw/PictureSelector  简介:包含:多选.单选.拍照.预览.裁剪:兼容大图,兼容 7.0 更多: 作者    提 Bug    ...

  3. Android 仿微信 QQ 图片裁剪,赶紧收藏起来!

    在正文之前,先公布下周三的送书中奖名单,点赞前15名为: 码不停蹄 િ?ી.zdxོ.gaolhjy.C.Aiden_ryan.进进.WhenSun .明($?.啊鑫.久伴.街 景 -.小石头.纸.冯 ...

  4. Android仿微信选择图片

    因为需要上传图片,选择图片的效果微信效果很好,所以我在网上找了一些仿微信的例子,但是都不是很全,所以我找了几个并和在一起,效果还行,不废话了上代码. 首先,是一个九宫格显示图片的页面.因为我的代码是f ...

  5. android 微信相册功能,Android仿微信选择图片和拍照功能

    本文实例为大家分享了 Android微信选择图片的具体代码,和微信拍照功能,供大家参考,具体内容如下 1.Android6.0系统,对于权限的使用都是需要申请,选择图片和拍照需要申请Manifest. ...

  6. Android仿微信聊天图片缩略图裁剪

    本文地址:http://blog.csdn.net/Jaden_hool/article/details/49642297 效果图: 1.横图原图: 横图聊天界面缩略图: 2.竖图原图: 竖图聊天界面 ...

  7. Android仿微信发图片的样式,做IM的同学的病有救了

    一:前言 最近在搞IM,真的特别痛苦.脑袋大,对于我这种菜鸟来说太难了,比现在社会娶个媳妇还难,硬着头皮搞,终于文字,语音,表情搞完了,开始搞图片,看着微信发的图片跟文字,语音有点区别,因为它并没有外 ...

  8. Android 仿微信聊天图片

    前言 在微信的聊天界面中,发送图片的时候显示图片如下,下面就来看看如何实现 实现 首先我们自定义一个ImageView用于显示聊天界面中的图片. 自定义ChatImageView继承ImageView ...

  9. android 仿微信选取相册_Android 实现一个仿微信的图片选择器

    现在大部分的App都上传图片的功能,比如设置用户头像.聊天发送图片.发表动态.论坛帖子等.上传图片需要先从选择手机中选择要上传的图片,所以图片选择器在App中是很常见的组件,一般的手机都会自带一个图片 ...

最新文章

  1. 张量功率谱CAMB参数调试
  2. linux c/c++ 判断是否为中文(不包括中文符号,非正则)
  3. python有趣代码-wtfPython―Python中一组有趣微妙的代码【收藏】
  4. 01 c++常见面试题总结
  5. (十四)nodejs循序渐进-高性能游戏服务器框架pomelo之开发Treasures游戏
  6. 机器学习中的alpha学习率参数
  7. c# 多线程单例模式_线程安全C#单例模式
  8. 42表盘直径是从哪测量_手表的术语中,直径指的是外观直径还是表盘直径?
  9. 面试准备:简历中项目剖析
  10. 程序员写了个开源软件,完成了舔狗的绝地反杀...
  11. 【Codeforces Round #420 (Div. 2) B】Okabe and Banana Trees
  12. u盘文件变成快捷方式怎么恢复,恢复U盘文件的五种方法
  13. 淘宝商品详情页API接口、淘宝商品销量API接口、淘宝商品列表API接口、淘宝APP详情API接口、淘宝详情API接口
  14. power bi 雷达图_星载雷达与C波段地基雷达数据一致性个例分析 | 新文速递
  15. RabbitMQ-三、Java使用--3、路由选择 (Routing)
  16. 滴滴出行大数据数仓实战
  17. termux文件导入导出
  18. Zookeeper安装与可视化客户端详细使用教程
  19. ERP的概念、简介与发展历程
  20. 没有钱该怎么创业?没钱创业依旧可以赚钱

热门文章

  1. 多传感器融合定位(二)——基于地图的定位
  2. 【Python金融量化】零基础如何开始学?
  3. 2021「AI中国」评选
  4. 计算火车运行时间 本题要求根据火车的出发时间和达到时间,编写程序计算整个旅途所用的时间
  5. 大学物理第三版朱峰课后答案详解_大学物理朱峰答案
  6. python获得股票数据并画图
  7. IDEA2021版本的安装和使用教程
  8. C# 获取 checkbox选中的值
  9. 计算机毕业设计SSM大学生健康管理系统的设计与实现【附源码数据库】
  10. element清除表单校验