Android基于PhotoView实现的头像/圆形裁剪控件

前言

常见的图片裁剪有两种,一种是图片固定,裁剪框移动放缩来确定裁剪区域,早期见的比较多,缺点在于不能直接预览裁剪后的效果;还有一种现在比较普遍了,就是裁剪框固定,直接拖动缩放图片,便于预览裁剪结果。

我做的这个控件属于后者。一般来说,做图片裁剪的思路无外乎是先监听手势,获取坐标,再对图片变形,最后确定裁剪区域的坐标对位图进行裁剪,最后保存图片到本地。我嘛还是个技术小白,一想到要监控手势这些就头疼,碰巧项目之前为了做查看大图而引入了大名鼎鼎的第三方图片查看控件——PhotoView(使用步骤参考这篇文章:Android PhotoView使用步骤实例详解)。于是转念一想,能不能把到图片变形为止的前几步交给PhotoView来搞定,我只要负责确定确定裁剪区域后面这几步呢。后来掉了好几个坑导致偷懒也没轻松多少其实ε=(´ο`*)))唉~

先简要介绍一下设计思路,如上图所示,主要分为两部分,上层是遮罩(也可以理解为是裁剪框),用于预览裁剪后的效果;下层是PhotoView,这里多包了一层改为正方形显示。

下面是遮罩的代码,比较简单,这里就不赘述了。

/**
 * Created by MandyLu on 2018/7/14.
 * 圆形裁剪框
 */
public class CircleCropView extends View {
 public final int CIRCLE_MARGIN = 50;

 public CircleCropView(Context context) {
 super(context);
 }

 public CircleCropView(Context context, @Nullable AttributeSet attrs) {
 super(context, attrs);
 }

 public CircleCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 }

 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
 public CircleCropView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
 super(context, attrs, defStyleAttr, defStyleRes);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, widthMeasureSpec);
 }

 @RequiresApi(api = Build.VERSION_CODES.O)
 @Override
 protected void onDraw(Canvas canvas) {
 canvas.save();

 Path path = new Path();
 Rect viewDrawingRect = new Rect();
 getDrawingRect(viewDrawingRect);

 float radius = viewDrawingRect.width() / 2 - CIRCLE_MARGIN;
 path.addCircle(viewDrawingRect.left + radius + CIRCLE_MARGIN,
 viewDrawingRect.top + radius + CIRCLE_MARGIN, radius, Path.Direction.CW);

 Paint outsidePaint = new Paint();
 outsidePaint.setAntiAlias(true);
 outsidePaint.setARGB(151, 0, 0, 0);

 canvas.clipPath(path, Region.Op.DIFFERENCE);
 canvas.drawRect(viewDrawingRect, outsidePaint);
 canvas.restore();
 }
}

SquarePhotoView只是在PhotoView的基础上改了长宽,重写一下onMeasure方法即可:

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 super.onMeasure(widthMeasureSpec, widthMeasureSpec);
 }

那么现在最关键的一步,就是从PhotoView获取当前图片显示区域的Drawable或Bitmap了。粗略看了一下PhotoView的函数,并没有找到能用的(囧)。解决第一个坑的笨办法就是,自己动手丰衣足食——直接拿原图的bitmap,然后问PhotoView要当前图片的变形矩阵,自个儿通过矩阵一步步变形拿到对应的位图。

思路其实是没问题的,然而第二个坑又出现了(囧)。这里的变形矩阵,我最早百度的结果是getSuppMatrix,源码我没有细看,但掉坑的过程中据我观察,猜测应该是对应最新一次的手势变形结果(不确定= =,也可能是其他坑综合导致的错误结果)。总之最后我查了一会源码,最终确定用的是getDisplayMatrix。

紧接着是第三个坑,坑多了就习惯了。矩阵中的XY位移量,我起初以为是显示区域中心相对于原图中心的位移,即如果仅有缩放操作的话,位移应该为0。但实际通过特殊位置(例如取四个顶点)的裁剪结果来看,这里的XY位移量实际最后显示区域左上角的点相对原点(即原图左上角)的位移,简单点说,可以把位移量作为最终显示区域左上角的坐标。

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

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

fun cropImage(){
 var degree = ImageUtils.readPictureDegree(imagePath)
 var bitmap = ImageUtils.getRotatedBitmap(BitmapFactory.decodeFile(imagePath),degree)

 var width: Int = 0
 var startX: Int = 0
 var startY: Int = 0
 if (bitmap.width < bitmap.height){
 startY = (bitmap.height - bitmap.width) / 2
 width = bitmap.width
 }else{
 startX = (bitmap.width - bitmap.height) / 2
 width = bitmap.height
 }

 var matrix = Matrix()
 photo_preview.getDisplayMatrix(matrix)//获取变形矩阵,直接取scaleX或translationX没用
 var values = FloatArray(9, {0.0f})
 matrix.getValues(values)

 var expWidth = Math.round(bitmap.width * values[0])//缩放x
 var expHeight = Math.round(bitmap.height * values[4])//缩放y

 var bitmap1 = Bitmap.createScaledBitmap(bitmap, expWidth, expHeight, false)

 val ratio = width * 1.0f / photo_preview.width
 startX = Math.round(startX * values[0] - values[2] * ratio)
 startY = Math.round(startY * values[4] - values[5] * ratio)
 var bitmap2 = Bitmap.createBitmap(bitmap1, startX, startY, width, width, null, false)

 saveImage(bitmap2)
 }

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

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

var startX: Int = 0
 var startY: Int = 0
 if (bitmap.width < bitmap.height){
 startY = (bitmap.height - bitmap.width) / 2
 width = bitmap.width
 }else{
 startX = (bitmap.width - bitmap.height) / 2
 width = bitmap.height
 }

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

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

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

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

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

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

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Android Studio实现带边框的圆形头像

    本文实例为大家分享了Android Studio实现带边框的圆形头像的具体代码,供大家参考,具体内容如下 效果显示: (没有边框的) (有边框的) 1.创建自定义ImagView控件 (1).没有边框的 package chenglong.activitytest.pengintohospital.utils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitma

  • Android PhotoView使用步骤实例详解

    Android PhotoView使用步骤实例详解 1.步骤一:在布局文件中的代码: <uk.co.senab.photoview.PhotoView android:id="@+id/img_showimgview" android:layout_width="match_parent" android:layout_height="match_parent" /> 2.步骤二:进行找到控件的id photoview = (Phot

  • Android 自定义圆形头像CircleImageView支持加载网络图片的实现代码

    在Android开发中我们常常用到圆形的头像,如果每次加载之后再进行圆形裁剪特别麻烦.所以在这里写一个自定义圆形ImageView,直接去加载网络图片,这样的话就特别的方便. 先上效果图 主要的方法 1.让自定义 CircleImageView 继承ImageView /** * 自定义圆形头像 * Created by Dylan on 2015/11/26 0026. */ public class CircleImageView extends ImageView { } 2.在构造方法中

  • Android手机拍照或选取图库图片作为头像

    package zhangpgil.photo; import java.io.File; import android.support.v7.app.ActionBarActivity; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; import android.content.Intent; import

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

    以前在eoe论坛中找过裁剪图片为圆形图片的方法,但是效果都不是很理想,这几天因为公司业务的要求,需要对头像进行裁剪以圆形的方式显示,这个方法是根据传入的图片的高度(height)和宽度(width)决定的,如果是 width <= height时,则会裁剪高度,裁剪的区域是宽度不变高度从顶部到宽度width的长度:如果 width > height,则会裁剪宽度,裁剪的区域是高度不变,宽度是取的图片宽度的中心区域,不过不同的业务需求,对裁剪图片要求不一样,可以根据业务的需求来调整裁剪的区域.

  • Android自定义控件仿QQ编辑和选取圆形头像

    android大家都有很多需要用户上传头像的需求,有的是选方形,有的是圆角矩形,有的是圆形. 首先我们要做一个处理图片的自定义控件,把传入的图片,经过用户选择区域,处理成一定的形状. 有的app是通过在图片上画一个矩形区域表示选中的内容,有的则是通过双指放大缩小,拖动图片来选取图片.圆形头像,还是改变图片比较好 圆形区域可调节大小. 这个自定义View的图像部分分为三个,背景图片,半透明蒙层,和亮色区域--还是直接贴代码得了 package com.example.jjj.widget; imp

  • Android仿微信QQ设置图形头像裁剪功能

    最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流. 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue)! 图片裁剪实现方式有两种,一种是利用系统自带的裁剪工具,一种是使用开源工具Cropper.本节就为大家带来如何使用系统自带的裁剪工具进行图片裁剪~ 还是先来个简单的运行图. 额,简单说下,我待会会把代码写成小demo分享给大家,在文章末尾会附上github链接,需要的可以自行下载~ 下面来简单分

  • Android一行代码实现圆形头像

    效果图 在开发APP中,经常要实现圆形头像,那么该如何实现呢? 要裁剪吗,要重写draw函数吗?不用,只用一行代码就可以实现 Glide实现圆形图像 Glide.with(mContext) .load(R.drawable.iv_image_header) .error(R.drawable.ic_error_default) .transform(new GlideCircleTransform(mContext)) .into(mImage); 其中load后为载入的图像,error后为出

  • Android实现本地上传图片并设置为圆形头像

    先从本地把图片上传到服务器,然后根据URL把头像处理成圆形头像. 因为上传图片用到bmob的平台,所以要到bmob(http://www.bmob.cn)申请密钥. 效果图: 核心代码: 复制代码 代码如下: public class MainActivity extends Activity {         private ImageView iv;         private String appKey="";                //填写你的Applicatio

  • Android第三方控件PhotoView使用方法详解

    PhotoView的简介: 这是一个图片查看库,实现图片浏览功能,支持pinch(捏合)手势或者点击放大缩小.支持在ViewPager中翻页浏览图片. PhotoView 是一款扩展自Android ImageView ,支持通过单点/多点触摸来进行图片缩放的智能控件.功能实用和强大. PhotoView的功能: 图片浏览查看 双指缩放 单点触摸缩放 图片缩放模式设置 基本用法: 导入jar包,布局XML里设置PhotoView 将ImageView传入PhotoViewAttacher 代码演

随机推荐