博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android Kotlin仿微信头像裁剪图片
阅读量:6272 次
发布时间:2019-06-22

本文共 13803 字,大约阅读时间需要 46 分钟。

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
() { 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
() { 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.

源码地址

关我我的公众号

滑板上的老砒霜

转载地址:http://lmlpa.baihongyu.com/

你可能感兴趣的文章
IDEA debug调试技巧
查看>>
非常好的Python学习资源收集整理
查看>>
java 图片等比压缩
查看>>
Oracle 创建普通用户,并赋予权限
查看>>
我的友情链接
查看>>
android工程目录结构,及相关文件获取方式(1)
查看>>
Vsftpd内网映射相关原理及配置
查看>>
Linux非对称路由
查看>>
在iOS 8中使用UIAlertController
查看>>
第2课:通过案例对SparkStreaming 透彻理解三板斧之二:解密SparkStreaming运行机制和架构...
查看>>
IOS开发—App 在 IOS 8 的simulator运行时,定位卡死bug解决
查看>>
windows 密钥登陆 linux
查看>>
IOS 录制视频
查看>>
limit检查
查看>>
Android Things 简介
查看>>
菜鸟学Linux 第049篇笔记 DNS log, zone, view
查看>>
菜鸟学Linux 第054篇笔记 建立加密的http
查看>>
ListView 的多选模式
查看>>
宏正自动科技发表新款8/16端口双滑轨LCD KVM多电脑切换器
查看>>
解决 Missing GL version
查看>>