Android Kotlin仿微信头像裁剪图片的方法示例

0.前言

最近突发了很多事情,又跟康仔跳票了,无可奈何,不好意思了。最近生活上有很多感悟,一个男人的牛逼就在于平衡工作,学习和家庭,这个点很难把握,既要保证家庭和睦,又要保证自己价值的实现从而避免堕入平庸,每个人的状况都是不一样的,没有什么经验是可以照搬的,怎么说呢,不断摸索吧。

1.分析

整个效果是仿照微信来做的,效果如图所示:

整个效果就是从图库选取一张图片,并进行裁剪,从图库选取没什么好说的,就说说怎么做的裁剪控件吧,这个裁剪控件就是ClipImageView,可以看到它有一个阴影遮罩,一个透明的框,还有图片的显示,以及可以移动图片。

2.代码

class ClipImageView(context: Context, attributeSet: AttributeSet?) : ImageView(context, attributeSet)
{

  private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)

  var clipWidth = 300
    set(value)
    {
      field = value
      if (isAttachedToWindow)
      {
        postInvalidate()
      }
    }

  var clipHeight = 300
    set(value)
    {
      field = value
      if (isAttachedToWindow)
      {
        postInvalidate()
      }
    }

  var minScale = 1.0f

  var maxScale = 1.0f

  private var rectColor = Color.BLACK

  private var lastTouchX = 0F

  private var lastTouchY = 0F

  private val transMatrix = Matrix()

  private var isTouching = false

  private var scale = 1.0f

  var onsaveClipImageListener: OnSaveClipImageListsner? = null

  private val scaleGestureDetectorListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener()
  {

    override fun onScale(detector: ScaleGestureDetector?): Boolean
    {
      val curScaleFactor = detector?.scaleFactor ?: 1.0f
      var curScale = scale * curScaleFactor
      curScale = if (curScale >= 1.0f) Math.min(maxScale, curScale) else Math.max(minScale, curScale)
      val scaleFactor = if (curScale > scale) 1 + (curScale - scale) / scale else 1.0f - (scale - curScale) / scale
      transMatrix.postScale(scaleFactor, scaleFactor, detector?.focusX
          ?: 0f, detector?.focusY ?: 0f)
      postInvalidate()
      scale = curScale
      return true
    }

    override fun onScaleEnd(detector: ScaleGestureDetector?)
    {
      super.onScaleEnd(detector)
    }
  }

  private var scaleGestureDetector: ScaleGestureDetector

  constructor(context: Context) : this(context, null)

  init
  {
    paint.strokeJoin = Paint.Join.ROUND
    scaleGestureDetector = ScaleGestureDetector(context, scaleGestureDetectorListener)
    if (attributeSet != null)
    {
      pareseAttributeSet(attributeSet)
    }
    setBackgroundColor(Color.WHITE)
  }

  private fun pareseAttributeSet(attributeSet: AttributeSet)
  {
    val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ClipImageView)
    clipWidth = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipWidth)
    clipHeight = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipHeight)
    rectColor = typedArray.getColor(R.styleable.ClipImageView_rect_color, rectColor)
    minScale = typedArray.getFloat(R.styleable.ClipImageView_min_scale, minScale)
    maxScale = typedArray.getFloat(R.styleable.ClipImageView_max_scale, maxScale)
    typedArray.recycle()
  }

  override fun layout(l: Int, t: Int, r: Int, b: Int)
  {
    super.layout(l, t, r, b)
    if (clipWidth > measuredWidth)
    {
      clipWidth = measuredWidth
    }
    if (clipHeight > measuredHeight)
    {
      clipHeight = measuredHeight
    }

  }

  override fun onTouchEvent(event: MotionEvent?): Boolean
  {
    if (event?.pointerCount ?: 1 >= 2)
    {
      isTouching = false
      return scaleGestureDetector.onTouchEvent(event)
    }
    else
    {
      when (event?.action)
      {
        MotionEvent.ACTION_DOWN ->
        {
          isTouching = true
          lastTouchX = event.x
          lastTouchY = event.y
        }

        MotionEvent.ACTION_MOVE ->
        {
          if (isTouching && event.pointerCount == 1)
          {
            val offsetX = event.x - lastTouchX
            val offsetY = event.y - lastTouchY
            transMatrix.postTranslate(offsetX, offsetY)
            lastTouchX = event.x
            lastTouchY = event.y
            postInvalidate()
          }
        }

        MotionEvent.ACTION_UP ->
        {
          isTouching = false
        }
      }
      return true
    }
  }

  override fun onDraw(canvas: Canvas?)
  {
    canvas?.let {
      val saveState = it.saveCount
      it.save()
      it.concat(transMatrix)
      super.onDraw(canvas)
      it.restoreToCount(saveState)
      drawMask(it)
      drawRect(it)

    }
  }

  private fun drawMask(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL
    paint.color = Color.parseColor("#A0000000")
    canvas.drawRect(0.0f, 0.0f, width.toFloat(), (height / 2 - clipHeight / 2).toFloat(), paint)
    canvas.drawRect((width / 2 + clipWidth / 2).toFloat(), (height / 2 - clipHeight / 2).toFloat(), width.toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 + clipHeight / 2).toFloat(), width.toFloat(), height.toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 - clipHeight / 2).toFloat(), (width / 2 - clipWidth / 2).toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
  }

  private fun drawRect(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL_AND_STROKE
    paint.color = rectColor
    paint.strokeWidth = 4.0f
    val offset = paint.strokeWidth / 2
    val left: Float = (width / 2 - clipWidth / 2).toFloat() - offset
    val top: Float = (height / 2 - clipHeight / 2).toFloat() - offset
    val right: Float = (width / 2 + clipWidth / 2).toFloat() + offset
    val bottom: Float = (height / 2 + clipHeight / 2).toFloat() + offset
    canvas.drawLine(left, top, right, top, paint)
    canvas.drawLine(right, top, right, bottom, paint)
    canvas.drawLine(left, bottom, right, bottom, paint)
    canvas.drawLine(left, top, left, bottom, paint)
  }

  interface OnSaveClipImageListsner
  {
    fun onImageFinishedSav()
  }

  inner class SaveTask(private val filePath: String) : AsyncTask<Unit, Unit, Unit>()
  {

    override fun doInBackground(vararg params: Unit?): Unit
    {
      saveClipImage(filePath)

    }

    override fun onPostExecute(result: Unit?)
    {
      super.onPostExecute(result)
      onsaveClipImageListener?.onImageFinishedSav()
    }
  }

  fun clipAndSaveImage(filePath: String)
  {
    SaveTask(filePath).execute()
  }

  private fun saveClipImage(filePath: String)
  {
    val clipBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val clipCanvas = Canvas(clipBitmap)
    draw(clipCanvas)
    try
    {
      val outputStream = FileOutputStream(filePath)
      val bitmap = Bitmap.createBitmap(clipBitmap, width / 2 - clipWidth / 2, height / 2 - clipHeight / 2, clipWidth, clipHeight, transMatrix, true)
      bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
      outputStream.close()
    }
    catch (e: IOException)
    {
      e.printStackTrace()
    }

  }
}

可以发现这段代码是继承自ImageView。

先看代码段

private fun pareseAttributeSet(attributeSet: AttributeSet)
  {
    val typedArray = context.obtainStyledAttributes(attributeSet, R.styleable.ClipImageView)
    clipWidth = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipWidth)
    clipHeight = typedArray.getDimensionPixelOffset(R.styleable.ClipImageView_clip_width, clipHeight)
    rectColor = typedArray.getColor(R.styleable.ClipImageView_rect_color, rectColor)
    minScale = typedArray.getFloat(R.styleable.ClipImageView_min_scale, minScale)
    maxScale = typedArray.getFloat(R.styleable.ClipImageView_max_scale, maxScale)
    typedArray.recycle()
  }

这里解析布局文件的里的属性,其中clipwidth和clipheight分别代表裁剪框的宽度和高度,minScale和maxScale是最小和最大的缩放程度。

override fun layout(l: Int, t: Int, r: Int, b: Int)
  {
    super.layout(l, t, r, b)
    if (clipWidth > measuredWidth)
    {
      clipWidth = measuredWidth
    }
    if (clipHeight > measuredHeight)
    {
      clipHeight = measuredHeight
    }

  }

在layout方法里设置clipWidth和clipHeight,防止设置值大于控件大小。

drawMask方法和drawRect方法是用来绘制遮罩层和裁剪框的,其中遮罩层就是四个方形,而裁剪框就是一个矩形的外框。

private fun drawMask(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL
    paint.color = Color.parseColor("#A0000000")
    canvas.drawRect(0.0f, 0.0f, width.toFloat(), (height / 2 - clipHeight / 2).toFloat(), paint)
    canvas.drawRect((width / 2 + clipWidth / 2).toFloat(), (height / 2 - clipHeight / 2).toFloat(), width.toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 + clipHeight / 2).toFloat(), width.toFloat(), height.toFloat(), paint)
    canvas.drawRect(0.0f, (height / 2 - clipHeight / 2).toFloat(), (width / 2 - clipWidth / 2).toFloat(), (height / 2 + clipHeight / 2).toFloat(), paint)
  }

  private fun drawRect(canvas: Canvas)
  {
    paint.style = Paint.Style.FILL_AND_STROKE
    paint.color = rectColor
    paint.strokeWidth = 4.0f
    val offset = paint.strokeWidth / 2
    val left: Float = (width / 2 - clipWidth / 2).toFloat() - offset
    val top: Float = (height / 2 - clipHeight / 2).toFloat() - offset
    val right: Float = (width / 2 + clipWidth / 2).toFloat() + offset
    val bottom: Float = (height / 2 + clipHeight / 2).toFloat() + offset
    canvas.drawLine(left, top, right, top, paint)
    canvas.drawLine(right, top, right, bottom, paint)
    canvas.drawLine(left, bottom, right, bottom, paint)
    canvas.drawLine(left, top, left, bottom, paint)
  }

接着看如何让图片随手指移动和缩放,这里说一下transMatrix,这个是Matrix类,通过它应用到Canvas来实现缩放和移动。

override fun onTouchEvent(event: MotionEvent?): Boolean
  {
    if (event?.pointerCount ?: 1 >= 2)
    {
      isTouching = false
      return scaleGestureDetector.onTouchEvent(event)
    }
    else
    {
      when (event?.action)
      {
        MotionEvent.ACTION_DOWN ->
        {
          isTouching = true
          lastTouchX = event.x
          lastTouchY = event.y
        }

        MotionEvent.ACTION_MOVE ->
        {
          if (isTouching && event.pointerCount == 1)
          {
            val offsetX = event.x - lastTouchX
            val offsetY = event.y - lastTouchY
            transMatrix.postTranslate(offsetX, offsetY)
            lastTouchX = event.x
            lastTouchY = event.y
            postInvalidate()
          }
        }

        MotionEvent.ACTION_UP ->
        {
          isTouching = false
        }
      }
      return true
    }
  }

当两个手指触摸时,由移动事件有ScaleGestureDetector处理缩放,否则进行移动。

先看移动:

将移动的距离应用到transMatrix,并调用postInvalidate()重新绘制。

再看缩放处理

private val scaleGestureDetectorListener = object : ScaleGestureDetector.SimpleOnScaleGestureListener()
  {

    override fun onScale(detector: ScaleGestureDetector?): Boolean
    {
      val curScaleFactor = detector?.scaleFactor ?: 1.0f
      var curScale = scale * curScaleFactor
      curScale = if (curScale >= 1.0f) Math.min(maxScale, curScale) else Math.max(minScale, curScale)
      val scaleFactor = if (curScale > scale) 1 + (curScale - scale) / scale else 1.0f - (scale - curScale) / scale
      transMatrix.postScale(scaleFactor, scaleFactor, detector?.focusX
          ?: 0f, detector?.focusY ?: 0f)
      postInvalidate()
      scale = curScale
      return true
    }

    override fun onScaleEnd(detector: ScaleGestureDetector?)
    {
      super.onScaleEnd(detector)
    }
  }

在SimpleOnScaleGestureListener的onScale方法处理缩放,将缩放因子应用到transMatrix,并调用postInvalidate()重新绘制。

接下重点就是onDraw方法:

override fun onDraw(canvas: Canvas?)
  {
    canvas?.let {
      val saveState = it.saveCount
      it.save()
      it.concat(transMatrix)
      super.onDraw(canvas)
      it.restoreToCount(saveState)
      drawMask(it)
      drawRect(it)

    }
  }

先调用save,保存当前画布状态,之后应用transMatrix,缩放和移动画布,然后调用ImageView的onDraw()方法,也就是父类的方法,用来绘制图片,因为绘制遮罩层和裁剪框不移动,所以恢复画布状态后进行绘制。

最后就是裁剪图片了

inner class SaveTask(private val filePath: String) : AsyncTask<Unit, Unit, Unit>()
  {

    override fun doInBackground(vararg params: Unit?): Unit
    {
      saveClipImage(filePath)

    }

    override fun onPostExecute(result: Unit?)
    {
      super.onPostExecute(result)
      onsaveClipImageListener?.onImageFinishedSav()
    }
  }

  fun clipAndSaveImage(filePath: String)
  {
    SaveTask(filePath).execute()
  }

  private fun saveClipImage(filePath: String)
  {
    val clipBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val clipCanvas = Canvas(clipBitmap)
    draw(clipCanvas)
    try
    {
      val outputStream = FileOutputStream(filePath)
      val bitmap = Bitmap.createBitmap(clipBitmap, width / 2 - clipWidth / 2, height / 2 - clipHeight / 2, clipWidth, clipHeight, transMatrix, true)
      bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)
      outputStream.close()
    }
    catch (e: IOException)
    {
      e.printStackTrace()
    }

  }

可以看到启动了一个AsyncTask用来裁剪和保存Bitmap,其中saveClipImage就是重新构建了一个画布,并传入bitmap,重新调用draw方法,将数据信息保存到bitmap,然后裁剪bitmap并存入文件。

3.源码地址 GitHub

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android实现从本地图库/相机拍照后裁剪图片并设置头像

    玩qq或者是微信的盆友都知道,这些聊天工具里都要设置头像,一般情况下大家的解决办法是从本地图库选择图片或是从相机拍照,然后根据自己的喜爱截取图片.上述过程已经实现好了,最后一步我加上了把截取好的图片在保存到本地的操作,来保存头像.为了大家需要,下面我们小编把完整的代码贴出来供大家参考. 先给大家展示效果图: 代码部分: 布局代码(其实就是两个按钮和一个ImageView来显示头像) <LinearLayout xmlns:android="http://schemas.android.co

  • Android 编辑头像功能简单实现实例(图片选取,裁剪)

    本文介绍了Android 编辑头像功能的简单实例,可以实现拍照,图片选取,裁剪. 拍照 public static void startCamera(Fragment fragment){ File file=new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)+ File.separator+"user_icon.jpg"); Intent intent=new Inten

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

    前言 常见的图片裁剪有两种,一种是图片固定,裁剪框移动放缩来确定裁剪区域,早期见的比较多,缺点在于不能直接预览裁剪后的效果:还有一种现在比较普遍了,就是裁剪框固定,直接拖动缩放图片,便于预览裁剪结果. 我做的这个控件属于后者.一般来说,做图片裁剪的思路无外乎是先监听手势,获取坐标,再对图片变形,最后确定裁剪区域的坐标对位图进行裁剪,最后保存图片到本地.我嘛还是个技术小白,一想到要监控手势这些就头疼,碰巧项目之前为了做查看大图而引入了大名鼎鼎的第三方图片查看控件--PhotoView(使用步骤参考

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

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

  • Android Kotlin仿微信头像裁剪图片的方法示例

    0.前言 最近突发了很多事情,又跟康仔跳票了,无可奈何,不好意思了.最近生活上有很多感悟,一个男人的牛逼就在于平衡工作,学习和家庭,这个点很难把握,既要保证家庭和睦,又要保证自己价值的实现从而避免堕入平庸,每个人的状况都是不一样的,没有什么经验是可以照搬的,怎么说呢,不断摸索吧. 1.分析 整个效果是仿照微信来做的,效果如图所示: 整个效果就是从图库选取一张图片,并进行裁剪,从图库选取没什么好说的,就说说怎么做的裁剪控件吧,这个裁剪控件就是ClipImageView,可以看到它有一个阴影遮罩,一

  • Android 高仿微信朋友圈动态支持双击手势放大并滑动查看图片效果

    最近参与了开发一款旅行APP,其中包含实时聊天和动态评论功能,终于耗时几个月几个伙伴完成了,今天就小结一下至于实时聊天功能如果用户不多的情况可以scoket实现,如果用户万级就可以采用开源的smack + opnefile实现,也可以用mina开源+XMMP,至于怎么搭建和实现,估计目前github上一搜一大把,至于即时通讯怕误人子弟,暂且不做介绍,现就把实现的一个微信朋友圈的小功能介绍一下. 先上效果图: 一拿到主流的UI需求,大致分析下,需要我ListView嵌套Gridview,而grid

  • Android 高仿微信语音聊天页面高斯模糊(毛玻璃效果)

    目前的应用市场上,使用毛玻璃效果的APP随处可见,比如用过微信语音聊天的人可以发现,语音聊天页面就使用了高斯模糊效果. 先看下效果图: 仔细观察上图,我们可以发现,背景图以用户头像为模板,对其进行了高斯模糊,并把它作为整个页面的背景色. 关于Android如何快速实现高斯模糊(毛玻璃效果),网上一堆相关介绍,可参考下面文章一种快速毛玻璃虚化效果实现–Android. 下面直接给出模糊化工具类(已验证可行): import android.graphics.Bitmap; /** * 快速模糊化工

  • Android高仿微信表情输入与键盘输入详解

    最近公司在项目上要使用到表情与键盘的切换输入,自己实现了一个,还是存在些缺陷,比如说键盘与表情切换时出现跳闪问题,这个相当困扰我,不过所幸在Github(其中一个不错的开源项目,其代码整体结构很不错)并且在论坛上找些解决方案,再加上我也是研究了好多个开源项目的代码,最后才苦逼地整合出比较不错的实现效果,可以说跟微信基本一样(嘿嘿,只能说目前还没发现大Bug,若发现大家一起日后慢慢完善,这里我也只是给出了实现方案,拓展其他表情我并没有实现哈,不过代码中我实现了一个可拓展的fragment模板以便大

  • Android 高仿微信朋友圈拍照上传功能

    模仿微信朋友圈发布动态,输入文字支持文字多少高度自增,有一个最小输入框高度,输入文字有限制,不过这些都很easy! 1. PhotoPicker的使用 这是一个支持选择多张图片,点击图片放大,图片之间左右滑动互相切换的库,同时支持图片删除的库,效果类似微信. (1) 添加PhotoPicker的架包 (2) 使用 选择图片:安卓6.0以后需要在代码中添加读写sd卡和相机的权限 当然清单文件中也需要添加的 PhotoPicker.builder() .setPhotoCount(maxPhoto)

  • Android高仿微信聊天界面代码分享

    微信聊天现在非常火,是因其界面漂亮吗,哈哈,也许吧.微信每条消息都带有一个气泡,非常迷人,看起来感觉实现起来非常难,其实并不难.下面小编给大家分享实现代码. 先给大家展示下实现效果图: OK,下面我们来看一下整个小项目的主体结构: 下面是Activity的代码: package com.way.demo; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import jav

  • Android 高仿微信支付数字键盘功能

    现在很多app的支付.输入密码功能,都已经开始使用自定义数字键盘,不仅更加方便.其效果着实精致. 下面带着大家学习下,如何高仿微信的数字键盘,可以拿来直接用在自身的项目中. 先看下效果图: 1. 自定义布局 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

  • Android仿微信主界面的实现方法

    本文实例为大家分享了Android模仿微信主界面展示的具体代码,供大家参考,具体内容如下 先看一下效果图 实现的原理: ViewPager+FragmentPagerAdapter 主界面可分为三部分: top标题栏就是一个TextView 中间的ViewPager作为显示的容器,填充Fragment bottom是一个RadioGroup 这里为了布局的优化,将top和bottom抽取出来 ,然后用include将其导入主布局,如下 <LinearLayout xmlns:android=&quo

  • Android实现手势划定区域裁剪图片

    本文实例为大家分享了Android实现手势划定区域裁剪图片的具体代码,供大家参考,具体内容如下 需求: 拍照,然后对图片进行处理,划定矩形区域,将矩形区域裁剪下来 思路: 1.使用系统相机拍照,拍完返回,对图片进行压缩和存储. 2.新建一个activity处理图片裁剪,利用自定义view在画布上画出矩形区域. 3.根据坐标信息生成裁剪图片并存储. 部分核心代码: 1.调用系统相机拍照 String IMAGE_PATH = Environment.getExternalStorageDirect

  • Android实现使用微信登录第三方APP的方法

    本文实例讲述了Android实现使用微信登录第三方APP的方法.分享给大家供大家参考,具体如下: 使用微信登录APP,免去注册过程,现在已经有很多的类似应用了.集成该功能过程不复杂,但还是有一些地方需要注意的. 开始之前,需要做下面的准备工作. 1.到微信开放平台注册你的APP,并申请开通微信登录的权限.参考这里: https://open.weixin.qq.com// 2.下载Android SDK和签名查看工具,请参考: https://open.weixin.qq.com/cgi-bin

随机推荐