基于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位移量实际最后显示区域左上角的点相对原点(即原图左上角)的位移,简单点说,可以把位移量作为最终显示区域左上角的坐标。

然后我就迎来了第四个坑(??)。这个坑现在回头看其实是很简单不应该栽进去的,然而当时还没想通的时候确实很慌(唉)。这个坑的问题就出在,Matrix里的值是基于手势的,也就是说,是基于屏幕像素(换句话说,是基于实际显示的图片)的。而对位图进行裁剪时,是基于原图像素的。那么这里还存在一个为了正常显示而导致的缩放比例的问题,例如原图是3000x4000,由于屏幕分辨率是1080*1920,那么实际显示时,图片是缩小了的,这个比例是9/25。所以在裁剪的过程中,需要把位移量再放大25/9倍进行还原。

  下面是裁剪部分的关键代码(最后偷了一下懒,没有裁圆形,只是用CIrcleImageView显示):

 1   fun cropImage(){
 2         var degree = ImageUtils.readPictureDegree(imagePath)
 3         var bitmap = ImageUtils.getRotatedBitmap(BitmapFactory.decodeFile(imagePath),degree)
 4
 5         var width: Int = 0
 6         var startX: Int = 0
 7         var startY: Int = 0
 8         if (bitmap.width < bitmap.height){
 9             startY = (bitmap.height - bitmap.width) / 2
10             width = bitmap.width
11         }else{
12             startX = (bitmap.width - bitmap.height) / 2
13             width = bitmap.height
14         }
15
16         var matrix = Matrix()
17         photo_preview.getDisplayMatrix(matrix)//获取变形矩阵,直接取scaleX或translationX没用
18         var values = FloatArray(9, {0.0f})
19         matrix.getValues(values)
20
21         var expWidth = Math.round(bitmap.width * values[0])//缩放x
22         var expHeight = Math.round(bitmap.height * values[4])//缩放y
23
24         var bitmap1 = Bitmap.createScaledBitmap(bitmap, expWidth, expHeight, false)
25
26         val ratio = width * 1.0f / photo_preview.width
27         startX =  Math.round(startX * values[0] - values[2] * ratio)
28         startY = Math.round(startY * values[4] - values[5] * ratio)
29         var bitmap2 = Bitmap.createBitmap(bitmap1, startX, startY, width, width, null, false)
30
31         saveImage(bitmap2)
32     }

  这里还有几个小坑需要解释一下:

  1. 读取bitmap时需要注意一下角度。这个是我在裁剪本地图片和网络图片的时候发现的,有些是正的有些就是转了90度。每个手机也不一定一样,所以保险起见,需要从图片的EXIF信息里面获取需要旋转的角度,然后再进一步处理。
  2. 我这里因为最终显示的是正方形,而且选的scaleType是centerCrop。所以默认就是显示中间的那一块。所以裁减时的原点也需要从正方形的左上角开始。这里是计算两种情况下的原点坐标:

    1         var startX: Int = 0
    2         var startY: Int = 0
    3         if (bitmap.width < bitmap.height){
    4             startY = (bitmap.height - bitmap.width) / 2
    5             width = bitmap.width
    6         }else{
    7             startX = (bitmap.width - bitmap.height) / 2
    8             width = bitmap.height
    9         }    

    缩放操作后,原点坐标也随之变换,乘以相应的缩放比例,再根据相应的位移量确定裁剪区域的位置。

  3. 原本想直接使用Matrix进行变形,失败(原因不明)。查看别的裁剪控件源码,决定使用createScaledBitmap来进行方法操作。

  最后还是要检讨一下:耍了小聪明想抄点近路,结果因为不熟悉源码,遇到坑的时候也只能当成黑盒;只能通过不断实验来猜测问题所在,反倒是花了更多时间,得不偿失了。以后有时间的时候,还是应该仔细研究源码,踏踏实实从原理出发解决问题(* ̄︶ ̄)~

最后,感谢几位博主的无私分享,特此鸣谢~

>>>Android Bitmap 常见的几个操作:缩放,裁剪,旋转,偏移

>>>Android ImageCropper 矩形 圆形 裁剪框

>>>Android裁剪图片为圆形图片的实现原理与代码

原文地址:https://www.cnblogs.com/mandylu2018/p/9385572.html

时间: 2024-10-11 16:59:45

基于PhotoView的头像/圆形裁剪控件的相关文章

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

拍照--裁剪,或者是选择图片--裁剪,是我们设置头像或上传图片时经常需要的一组操作.上篇讲了Camera的使用,这篇讲一下我对图片裁剪的实现. 背景 下面的需求都来自产品. 裁剪图片要像微信那样,拖动和放大的是图片,裁剪框不动. 裁剪框外的内容要有半透明黑色遮罩. 裁剪框下面要显示一行提示文字(这点我至今还是持保留意见的). 在Android中,裁剪图片的控件库还是挺多的,特别是github上比较流行的几个,都已经进化到比较稳定的阶段,但比较遗憾的是它们的裁剪过程是拖动或缩放裁剪框,于是只好自己

Web应用程序开发,基于Ajax技术的JavaScript树形控件

感谢http://www.cnblogs.com/dgrew/p/3181769.html#undefined 在Web应用程序开发领域,基于Ajax技术的JavaScript树形控件已经被广泛使用,它用来在Html页面上展现具有层次结构的数据项. 目前市场上常见的JavaScript框架及组件库中均包含自己的树形控件,例如jQuery.Dojo.YUI.Ext JS等,还有一些独立的树形控件,例如dhtmlxTree等,这些树形控件完美的解决了层次数据的展示问题. 展示离不开数据,树形控件主要

基于Qt的第三方库和控件

====================== 基于Qt的第三方库和控件 ======================     libQxt --------   http://dev.libqxt.org/libqxt/wiki/Home   按照文档中所流露的意思,libQxt实现了一些“Qt本来就应该有但实际上没有”的功能. 对应Qt相应的Module,Qxt有 QxtCore.QxtGui.QxtNetwork.QxtSql等模块作为Qt功能的 补充,此外,还提供了QxtWeb(Web S

基于浏览器的文档处理控件TX Text Control .NET Server for WP

TX Text Control .NET Server for WPF控件为用于ASP.NET服务器环境提供一个完全可编程的文字处理引擎,并且包含一个WPF客户端版本 具体功能: 合并Microsoft Word模板,生成打印就绪的PDF文件 在浏览器中以所见即所得模式编辑处理文档 从同一个文档的每一页中生成图片或meta文件 使用来自不同源的数据从零开始通过编程生成文档 在所有支持的格式之间转换文档 完全独立于 Microsoft Word, Adobe Acrobat,及其他第三方软件 一台

基于Bootstrap的JQuery TreeView树形控件,数据支持json字符串、list集合(MVC5)

BZ第一次自己写博客,心情好激动!!BZ也是小菜,本文如果有什么不对的地方,希望大神们多多指教,也希望和我一样的小菜多多学习.BZ在这里谢过各位. BZ最近看了很多博友的有关TreeView的博客,发现很多都是WebForm.JQuery的.因为BZ使用的是MVC的原因,所以决定写一写关于MVC和Bootstrap的TreeView. PS:基于Bootstrap的JQuery TreeView树形控件,JQuery版本为2.1.1(下载网上的基于Bootstrap的JQuery TreeVie

基于wince的MFC Tab Control控件的使用

1,先建立一个对话框MFC应用程序,然后在工具箱里面把Tab Control控件放到对话框中的合适位置上. 再在对话框类中,声明一个CTabCtrl变量: CTabCtrl m_tab; 变量m_tab用来与对话框中的Tab Control控件交互,为此要在DoDataExchange函数中加入DDX_Control语句: 1 //{{AFX_DATA_MAP(CTABDlg) 2 DDX_Control(pDX, IDC_TAB, m_tab); 3 //}}AFX_DATA_MAP IDC_

Stimulsoft Reports Designer.Silverlight是一个基于web的报表设计器控件

Stimulsoft Reports Designer.Silverlight是一个基于web的报表设计器控件,通过使用它您可以直接在web浏览器中更改您的报表控件.该产品使用Silverlight技术和ASP.NET开发.它不需要开发人员编写复杂的代码或很长的组件设置.您在服务器上使用的是一个简单的ASP.NET组件.Silverlight组件在客户端上运行.Stimulsoft Reports Designer.Silverlight拥有一个时尚的用户界面,加载迅速,运行速度快,并拥有丰富的

android 自定义圆形imageview控件

首先,定义定义圆形Imageview类: import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDu

一个简单的可控的头像列表叠加控件

一个简单的可控的头像列表叠加控件 需求:评论/点赞头像横向排列,第N个叠加在第N+1个上面,并且N小于一个固定的数分析:1.假设N=4:头像列表最多显示4个,不足4个,有几个显示几个:2.头像第N个叠加在第N+1个上面,无法使用margin负数实现(叠加顺序不对) 1.控件实现代码要点:控件可以横向滑动,继承HorizontalScrollView:创建RelativeLayout存放头像集合: public class SDAvatarListLayout extends Horizontal