当前位置: 首页 > 编程笔记 >

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

束向荣
2023-03-14
本文向大家介绍Android Kotlin仿微信头像裁剪图片的方法示例,包括了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

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

 类似资料:
  • 本文向大家介绍Android仿微信QQ设置图形头像裁剪功能,包括了Android仿微信QQ设置图形头像裁剪功能的使用技巧和注意事项,需要的朋友参考一下 最近在做毕业设计,想有一个功能和QQ一样可以裁剪头像并设置圆形头像,额,这是设计狮的一种潮流。 而纵观现在主流的APP,只要有用户系统这个功能,这个需求一般都是在(bu)劫(de)难(bu)逃(xue)! 图片裁剪实现方式有两种,一种是利用系统自带

  • AvatarCropper 头像裁剪 平台差异说明 App H5 微信小程序 支付宝小程序 百度小程序 头条小程序 QQ小程序 √ √ √ √ √ √ √ 基本使用 组件使用流程: 打开头像裁剪页面,同时传递配置基本参数(已默认配置好最优参数) 选取图片,调整图片合适位置和大小,确定裁剪并返回此裁剪结果 在原始页面监听uAvatarCropper事件,获得裁剪结果 <template> <vie

  • 我试图在从图库中选择图像后使用intent来裁剪图像。以下是我的代码片段 在这里,我使用PICK_IMAGE_REQUEST意图句柄调用上面的代码段 由于我在裁剪后使用了相同的意图,即PICK_IMAGE_REQUEST,可能会出现什么问题

  • 本文向大家介绍Android仿微信群聊头像,包括了Android仿微信群聊头像的使用技巧和注意事项,需要的朋友参考一下 工作中需要实现仿钉钉群头像的一个功能,就是个人的头像拼到一起显示,看了一下市场上的APP好像微信的群聊头像是组合的,QQ的头像不是,别的好像也没有了。 给大家分享一下怎么实现的吧。首先我们先看一下效果图: 好了,下面说一下具体怎么实现的: 实现思路 1.首先获取Bitmap图片(

  • 我将<code>背景 1.back_xml: 2.瓷砖.xml 现在,我将back.xml设置为< code >背景以< code>LinearLayout工作正常。 我需要有一个圆角,以及它的边框。但是我只有圆角的边框,而不是图像,我的代码中有什么问题吗? 这是我的照片:

  • 本节,我们将裁剪图像的一部分,然后把其结果绘制到画布上。 图3-2 裁剪图像 绘制步骤 按照以下步骤,裁剪图像的一部分,再把结果绘制到画布: 1. 定义画布上下文: window.onload  = function(){ var canvas  = document.getElementById("myCanvas"); var context  = canvas.getContext