在正文之前,先公布下周三的送书中奖名单,点赞前15名为:

码不停蹄 િ?ી、zdxོ、gaolhjy、C、Aiden_ryan、进进、WhenSun 。明($?、啊鑫、久伴、街 景 ~、小石头、纸、冯志远、梦之浮桥、文帆帆帆帆

请大家添加我的微信,然后把快递信息发我,我给大家寄书:

下面是正文部分

在平时开发中,经常需要实现这样的功能,拍照 - 裁剪,相册 - 裁剪。当然,系统也有裁剪的功能,但是由于机型,系统兼容性等问题,在实际开发当中,我们通常会自己进行实现。今天,就让我们一起来看看怎样实现。

这篇博客实现的功能主要有仿微信,QQ 上传图像裁剪功能,包括拍照,从相册选取。裁剪框的样式有圆形,正方形,九宫格。

主要讲解的功能点

  1. 使用说明

  2. 整体的实现思路

  3. 裁剪框的实现

  4. 图片缩放的实现,包括放大,缩小,移动,裁剪等

我们先来看看我们实现的效果图

使用说明

有两种调用方式

第一种

第一种,使用普通的 startActivityForResult 进行调用,并重写 onActivityResult 方法,在里面根据 requestCode 进行处理

 1ClipImageActivity.goToClipActivity(this, uri); 2@Override 3protected void onActivityResult(int requestCode, int resultCode, Intent intent) { 4    switch (requestCode) { 5        case REQ_CLIP_AVATAR:  //剪切图片返回 6            if (resultCode == RESULT_OK) { 7                final Uri uri = intent.getData(); 8                if (uri == null) { 9                    return;10                }11                String cropImagePath = FileUtil.getRealFilePathFromUri(getApplicationContext(), uri);121314    ----1516}

第二种

第二种调用 ClipImageActivity.goToClipActivity 方法,结果以 callBack 回调的方式返回回来,这种看起来比较直观点,个人也比较喜欢这种方法。它的实现原理是通过空白的 fragment 处理实现的,有兴趣的可以看我这一篇博客 Android Fragment 的妙用 - 优雅地申请权限和处理 onActivityResult

1ClipImageActivity.goToClipActivity(this, uri, new ActivityResultHelper.Callback() {2    @Override3    public void onActivityResult(int resultCode, Intent data) {45    }6});

整体实现思路

从上面的效果图我们可以看到,裁剪功能主要包括两大块

  1. 裁剪框

  2. 图片的缩放,移动,裁剪等

因此,为了方便日后的修改,我们将裁剪框的功能单独提取出来,图片缩放功能提出出来。即裁剪框单独一个 View。

下面,让我们一起来看看裁剪框功能的实现。

裁剪框功能的实现


裁剪框主要有两层,第一层,裁剪框的实现(包括圆形,长方形,九宫格形状),第二层,在裁剪区域上面盖上一层蒙层。

蒙层

蒙层的实现我们是通过 Xfermode 实现的

1xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_OUT);234//通过Xfermode的DST_OUT来产生中间的透明裁剪区域,一定要另起一个Layer(层)5canvas.saveLayer(0, 0, this.getWidth(), this.getHeight(), null, Canvas.ALL_SAVE_FLAG);67//设置背景8canvas.drawColor(Color.parseColor("#a8000000"));9paint.setXfermode(xfermode);

圆形裁剪框的实现

绘制圆形裁剪框很容易实现,主要确定圆心和半径即可

1//中间的透明的圆2canvas.drawCircle(this.getWidth() / 2, this.getHeight() / 2, clipRadiusWidth, paint);3//白色的圆边框4canvas.drawCircle(this.getWidth() / 2, this.getHeight() / 2, clipRadiusWidth, borderPaint);

正方形裁剪框的实现


绘制长方形的话主要要确定四个点的坐标 left ,top, right, botom。
很简单

1left = mHorizontalPadding;2top = this.getHeight() / 2 - clipWidth / 2;3right =   this.getWidth() - mHorizontalPadding;4botom = this.getHeight() / 2 + clipWidth / 2;
1//绘制中间白色的矩形蒙层2canvas.drawRect(mHorizontalPadding, this.getHeight() / 2 - clipWidth / 2,3        this.getWidth() - mHorizontalPadding, this.getHeight() / 2 + clipWidth / 2, paint);4//绘制白色的矩形边框5canvas.drawRect(mHorizontalPadding, this.getHeight() / 2 - clipWidth / 2,6        this.getWidth() - mHorizontalPadding, this.getHeight() / 2 + clipWidth / 2, borderPaint);

九宫格的


九宫格的绘制稍微繁琐一点,分三个步骤

  1. 绘制长方形边框

  2. 绘制九宫格引导线

  3. 绘制裁剪边框的是个直角

绘制长方形边框的这里就不说了,比较简单。

我们来看一下绘制九宫格引导线的

  • 绘制竖直方向两条线

  • 绘制水平方向两条线

 1private void drawGuidelines(@NonNull Canvas canvas, Rect clipRect) { 2 3    final float left = clipRect.left; 4    final float top = clipRect.top; 5    final float right = clipRect.right; 6    final float bottom = clipRect.bottom; 7 8    final float oneThirdCropWidth = (right - left) / 3; 910    final float x1 = left + oneThirdCropWidth;11    //引导线竖直方向第一条线12    canvas.drawLine(x1, top, x1, bottom, mGuidelinePaint);13    final float x2 = right - oneThirdCropWidth;14    //引导线竖直方向第二条线15    canvas.drawLine(x2, top, x2, bottom, mGuidelinePaint);1617    final float oneThirdCropHeight = (bottom - top) / 3;1819    final float y1 = top + oneThirdCropHeight;20    //引导线水平方向第一条线21    canvas.drawLine(left, y1, right, y1, mGuidelinePaint);22    final float y2 = bottom - oneThirdCropHeight;23    //引导线水平方向第二条线24    can

绘制四个直角的

 1private void drawCorners(@NonNull Canvas canvas, Rect clipRect) { 2 3    final float left = clipRect.left; 4    final float top = clipRect.top; 5    final float right = clipRect.right; 6    final float bottom = clipRect.bottom; 7 8    //简单的数学计算 9    final float lateralOffset = (mCornerThickness - mBorderThickness) / 2f;10    final float startOffset = mCornerThickness - (mBorderThickness / 2f);1112    //左上角左面的短线13    canvas.drawLine(left - lateralOffset, top - startOffset, left - lateralOffset, top + mCornerLength, mCornerPaint);14    //左上角上面的短线15    canvas.drawLine(left - startOffset, top - lateralOffset, left + mCornerLength, top - lateralOffset, mCornerPaint);1617    //右上角右面的短线18    canvas.drawLine(right + lateralOffset, top - startOffset, right + lateralOffset, top + mCornerLength, mCornerPaint);19    //右上角上面的短线20    canvas.drawLine(right + startOffset, top - lateralOffset, right - mCornerLength, top - lateralOffset, mCornerPaint);2122    //左下角左面的短线23    canvas.drawLine(left - lateralOffset, bottom + startOffset, left - lateralOffset, bottom - mCornerLength, mCornerPaint);24    //左下角底部的短线25    canvas.drawLine(left - startOffset, bottom + lateralOffset, left + mCornerLength, bottom + lateralOffset, mCornerPaint);2627    //右下角左面的短线28    canvas.drawLine(right + lateralOffset, bottom + startOffset, right + lateralOffset, bottom - mCornerLength, mCornerPaint);29    //右下角底部的短线30    canvas.drawLine(right + startOffset, bottom + lateralOffset, right - mCornerLength, bottom + lateralOffset, mCornerPaint);31}

图片裁剪框的实现到此讲解完毕,更多细节请参考 ClipView

图片的缩放,移动

实现原理简述

这里我们是通过 ClipViewLayout 实现的,它是 RelativeLayout 的子类,里面含有 ImageView 和 ClipView(裁剪框)。我们通过监听 ClipViewLayout 的 onTouchEvent 事件,设置 imageView 的图片矩阵。

我们先来了解一下,主要有三种模式,NONE,DRAG, ZOOM。NONE 表示初始模式,DRAG 表示拖拽模式,ZOOM 表示缩放模式

1private static final int NONE = 0;2//动作标志:拖动3private static final int DRAG = 1;4//动作标志:缩放5private static final int ZOOM = 2;

当我们多个手指按下的时候,加入两个手指之间的距离超过 10,此时我们认为进入 ZOOM 模式。DRAG 模式的话当我们手指按下的时候进入。NONE 模式,当我们手机抬起的时候,进入复位模式。

 1switch (event.getAction() & MotionEvent.ACTION_MASK) { 2    case MotionEvent.ACTION_DOWN: 3        Log.d(TAG, "onTouchEvent: ACTION_DOWN"); 4        mSavedMatrix.set(mMatrix); 5        //设置开始点位置 6        mStart.set(event.getX(), event.getY()); 7        mode = DRAG; 8        break; 9    case MotionEvent.ACTION_POINTER_DOWN:10        //开始放下时候两手指间的距离11        mOldDist = spacing(event);12        if (mOldDist > 10f) {13            mSavedMatrix.set(mMatrix);14            midPoint(mMid, event);15            mode = ZOOM;16        }17        break;18    case MotionEvent.ACTION_UP:19    case MotionEvent.ACTION_POINTER_UP:20        mode = NONE;21        break;

接下来我们一起来看一下,我们 action_move 的时候,我们是怎样进行移动和缩放的。

移动的话相对比较简单,首先它会计算出我们这一次 event 事件相对我们 action_down 时候 event 事件的偏移量 dx, dy。接着调用 mMatrix.postTranslate(dx, dy),进行矩阵的移动。最后,再检测是否超出边界。

 1case MotionEvent.ACTION_MOVE: 2    Log.d(TAG, "onTouchEvent: mode =" + mode); 3    if (mode == DRAG) { //拖动 4        mMatrix.set(mSavedMatrix); 5        float dx = event.getX() - mStart.x; 6        float dy = event.getY() - mStart.y; 7 8        mVerticalPadding = mClipView.getClipRect().top; 9        mMatrix.postTranslate(dx, dy);10        //检查边界11        checkBorder();12    } 13    ---14    mImageView.setImageMatrix(mMatrix);

边界检测 主要是检查缩放,移动后的图片矩阵的 left, top,right, bottom 是否在图片裁剪框之内,如果在的话,需要对图片矩阵进行移动。确保边界不在裁剪框之内。

 1/** 2 * 边界检测 3 */ 4private void checkBorder() { 5    RectF rect = getMatrixRectF(mMatrix); 6    float deltaX = 0; 7    float deltaY = 0; 8    int width = mImageView.getWidth(); 9    int height = mImageView.getHeight();10    // 如果宽或高大于屏幕,则控制范围 ; 这里的0.001是因为精度丢失会产生问题,但是误差一般很小,所以我们直接加了一个0.0111    if (rect.width() + 0.01 >= width - 2 * mHorizontalPadding) {12        // 图片矩阵的最左边 > 裁剪边框的左边13        if (rect.left > mHorizontalPadding) {14            deltaX = -rect.left + mHorizontalPadding;15        }16        // 图片矩阵的最右边 < 裁剪边框的右边17        if (rect.right < width - mHorizontalPadding) {18            deltaX = width - mHorizontalPadding - rect.right;19        }20    }21    if (rect.height() + 0.01 >= height - 2 * mVerticalPadding) {22        // 图片矩阵的 top > 裁剪边框的 top23        if (rect.top > mVerticalPadding) {24            deltaY = -rect.top + mVerticalPadding;25        }26        // 图片矩阵的 bottom < 裁剪边框的 bottom27        if (rect.bottom < height - mVerticalPadding) {28            deltaY = height - mVerticalPadding - rect.bottom;29        }30    }3132    Log.i(TAG, "checkBorder: deltaX=" + deltaX + " deltaY = " + deltaY);3334    mMatrix.postTranslate(deltaX, deltaY);35}

裁剪功能的实现

 1/** 2 * 获取剪切图 3 */ 4public Bitmap clip() { 5    mImageView.setDrawingCacheEnabled(true); 6    mImageView.buildDrawingCache(); 7    Rect rect = mClipView.getClipRect(); 8    Bitmap cropBitmap = null; 9    Bitmap zoomedCropBitmap = null;10    try {11        cropBitmap = Bitmap.createBitmap(mImageView.getDrawingCache(), rect.left, rect.top, rect.width(), rect.height());12        // 对图片进行压缩,这里因为 mPreViewW 与宽高是相等的,所以压缩比例是 1:1,可以根据需要自己调整13        zoomedCropBitmap = BitmapUtil.zoomBitmap(cropBitmap, mPreViewW, mPreViewW);14    } catch (Exception e) {15        e.printStackTrace();16    }17    if (cropBitmap != null) {18        cropBitmap.recycle();19    }20    // 释放资源21    mImageView.destroyDrawingCache();22    return zoomedCropBitmap;23}

题外话

这个 Demo 涉及到的 Android 技术点其实是蛮多的,可以说是麻雀虽小,五脏俱全。Android 7.0 图片拍照适配,6.0 动态权限申请,Android 使用空白 fragment 处理 onActivityResult,动态权限申请,自定义 View,View 的事件分发机制等等。

这篇博客主要是介绍个人认为比较重要的技术点,其他的可以自行取了解。最后,提供一下 demo 下载地址:https://github.com/gdutxiaoxu/clipimage

特别鸣谢

文中很多代码参考了以下两篇文章,在他们的基础之上进行了修改。由于时间的关系,并没有对裁剪框进行更多细节化的定制,比如图片比例,自定义属性的暴露等,有兴趣的话可以自己进行添加

https://github.com/wsy858/android-headimage-cliper

https://blog.csdn.net/lmj623565791/article/details/39761281

GitHub 地址

https://github.com/gdutxiaoxu/clipimage

推荐阅读
普通人如何提高格局
谈谈我最近的一些思考
谈谈程序员个人品牌的塑造

编程·思维·职场
欢迎扫码关注

Android 仿微信 QQ 图片裁剪,赶紧收藏起来!相关推荐

  1. android 仿微信头像裁剪,Android仿微信QQ设置图形头像裁剪功能

    最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流. 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue ...

  2. android标题栏不被顶上去,Android仿微信QQ聊天顶起输入法不顶起标题栏的问题

    在这记录一下输入法弹出的一系列问题,有的输入法弹出就把整个布局弹上去,有的输入法弹出布局不会有变化,有的输入法弹出遮盖输入框等等问题,网上也有很多说加着加那的,但是看一下都不是很完整,解决不了所有问题 ...

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

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

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

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

  5. Android仿微信选择图片

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

  6. Android仿微信QQ群头像生成

    先上效果图 qq效果的图片是在github上找的项目 https://github.com/kongnanlive/android-combination-avatar 仿微信群头像是自己鼓捣出来的 ...

  7. 仿微信头像图片裁剪并压缩到100K以下到本地

    逻辑:1.打开本地相机-并传入path路径           2.在onActivityResult中,判断File(path)是否为空,不为空则跳转到下个界面           PS:这里为什么 ...

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

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

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

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

最新文章

  1. 零样本风格迁移:多模态CLIP文本驱动图像生成
  2. BZOJ 3994: [SDOI2015]约数个数和 [莫比乌斯反演 转化]
  3. 开源 java CMS - FreeCMS2.6 静态化管理
  4. 2020总结--惟愿人间花满天
  5. ArcGIS.Server.9.2.DotNet自带例子分析(三、一)
  6. 计算机用户账户无法打开浏览器,请问怎么样禁止一个电脑用户使用IE浏览器
  7. 思源EMLOG文章页网址跳转插件V1.1
  8. 爬虫入门之urllib库详解(二)
  9. 第四课、Hello QT------------------狄泰软件学院
  10. bootstrap插件bootbox
  11. crmeb多商户二开crmeb类库二开文档services服务类【5】
  12. php使用grpc(windows环境下)
  13. scala在idea中的配置
  14. Android酷炫音乐律动动效部署到Maven仓库
  15. 【JavaSE】入门概述(1~41)
  16. java ODBC连接MYSQL数据库
  17. 扎好篱笆桩:三大运营商守住网络安全“命门”
  18. ti linux sdk 使用方法,关于AM335x 最新SDK ti-processor-sdk-linux-am335x-evm-06.03.00.106使用中遇到的问题...
  19. 2021最新大厂Java面试集合,顺利拿到offer
  20. Sharding-JDBC简单使用

热门文章

  1. ASP.net 母版页 外观设置/样式表设置
  2. 【C/C++基础】11_用户自定义数据类型
  3. 信息收集---zoomeye
  4. Harbor仓库开启https域名登陆报错问题
  5. 调制识别数据集RML2016.10a的调制信号参数(采样率)
  6. 鼠标滑动变色(discolor)
  7. 2023最新扫码连wifi-扫码挪车-聚合CPS返利多合一小程序源码
  8. 银行卡密码修改重置业务简介
  9. 北大毕业学姐自白:我为什么离开北京
  10. 支付宝当面付 F2FPay_Demo_Java详细解析