基于PhotoView的头像/圆形裁剪控件
常见的图片裁剪有两种,一种是图片固定,裁剪框移动放缩来确定裁剪区域,早期见的比较多,缺点在于不能直接预览裁剪后的效果;还有一种现在比较普遍了,就是裁剪框固定,直接拖动缩放图片,便于预览裁剪结果。
常见的图片裁剪有两种,一种是图片固定,裁剪框移动放缩来确定裁剪区域,早期见的比较多,缺点在于不能直接预览裁剪后的效果;还有一种现在比较普遍了,就是裁剪框固定,直接拖动缩放图片,便于预览裁剪结果。
我做的这个控件属于后者。一般来说,做图片裁剪的思路无外乎是先监听手势,获取坐标,再对图片变形,最后确定裁剪区域的坐标对位图进行裁剪,最后保存图片到本地。我嘛还是个技术小白,一想到要监控手势这些就头疼,碰巧项目之前为了做查看大图而引入了大名鼎鼎的第三方图片查看控件——PhotoView。于是转念一想,能不能把到图片变形为止的前几步交给PhotoView来搞定,我只要负责确定确定裁剪区域后面这几步呢。后来掉了好几个坑导致偷懒也没轻松多少其实ε=(´ο`*)))唉~
先简要介绍一下设计思路,如上图所示,主要分为两部分,上层是遮罩(也可以理解为是裁剪框),用于预览裁剪后的效果;下层是PhotoView,这里多包了一层改为正方形显示。
下面是遮罩的代码,比较简单,这里就不赘述了。
1 /** 2 * Created by MandyLu on 2018/7/14. 3 * 圆形裁剪框 4 */ 5 public class CircleCropView extends View { 6 public final int CIRCLE_MARGIN = 50; 7 8 public CircleCropView(Context context) { 9 super(context); 10 } 11 12 public CircleCropView(Context context, @Nullable AttributeSet attrs) { 13 super(context, attrs); 14 } 15 16 public CircleCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 17 super(context, attrs, defStyleAttr); 18 } 19 20 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) 21 public CircleCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { 22 super(context, attrs, defStyleAttr, defStyleRes); 23 } 24 25 @Override 26 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 27 super.onMeasure(widthMeasureSpec, widthMeasureSpec); 28 } 29 30 @RequiresApi(api = Build.VERSION_CODES.O) 31 @Override 32 protected void onDraw(Canvas canvas) { 33 canvas.save(); 34 35 Path path = new Path(); 36 Rect viewDrawingRect = new Rect(); 37 getDrawingRect(viewDrawingRect); 38 39 float radius = viewDrawingRect.width() / 2 - CIRCLE_MARGIN; 40 path.addCircle(viewDrawingRect.left + radius + CIRCLE_MARGIN, 41 viewDrawingRect.top + radius + CIRCLE_MARGIN, radius, Path.Direction.CW); 42 43 Paint outsidePaint = new Paint(); 44 outsidePaint.setAntiAlias(true); 45 outsidePaint.setARGB(151, 0, 0, 0); 46 47 canvas.clipPath(path, Region.Op.DIFFERENCE); 48 canvas.drawRect(viewDrawingRect, outsidePaint); 49 canvas.restore(); 50 } 51 }
SquarePhotoView只是在PhotoView的基础上改了长宽,重写一下onMeasure方法即可:
1 @Override 2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 3 super.onMeasure(widthMeasureSpec, widthMeasureSpec); 4 }
那么现在最关键的一步,就是从PhotoView获取当前图片显示区域的Drawable或Bitmap了。粗略看了一下PhotoView的函数,并没有找到能用的(囧)。解决第一个坑的笨办法就是,自己动手丰衣足食——直接拿原图的bitmap,然后问PhotoView要当前图片的变形矩阵,自个儿通过矩阵一步步变形拿到对应的位图。
思路其实是没问题的,然而第二个坑又出现了(囧)。这里的变形矩阵,我最早百度的结果是getSuppMatrix,源码我没有细看,但掉坑的过程中据我观察,猜测应该是对应最新一次的手势变形结果(不确定= =,也可能是其他坑综合导致的错误结果)。总之最后我查了一会源码,最终确定用的是getDisplayMatrix。
紧接着是第三个坑,坑多了就习惯了。矩阵中的XY位移量,我起初以为是显示区域中心相对于原图中心的位移,即如果仅有缩放操作的话,位移应该为0。但实际通过特殊位置(例如取四个顶点)的裁剪结果来看,这里的XY位移量实际最后显示区域左上角的点相对原点(即原图左上角)的位移,简单点说,可以把位移量作为最终显示区域左上角的坐标。
然后我就迎来了第四个坑(