重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
0.前言
成都創新互聯公司專注為客戶提供全方位的互聯網綜合服務,包含不限于做網站、成都網站建設、高青網絡推廣、成都微信小程序、高青網絡營銷、高青企業策劃、高青品牌公關、搜索引擎seo、人物專訪、企業宣傳片、企業代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;成都創新互聯公司為所有大學生創業者提供高青建站搭建服務,24小時服務熱線:18980820575,官方網址:www.cdcxhl.com
最近突發了很多事情,又跟康仔跳票了,無可奈何,不好意思了。最近生活上有很多感悟,一個男人的牛逼就在于平衡工作,學習和家庭,這個點很難把握,既要保證家庭和睦,又要保證自己價值的實現從而避免墮入平庸,每個人的狀況都是不一樣的,沒有什么經驗是可以照搬的,怎么說呢,不斷摸索吧。
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.源碼地址 GitHub
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持創新互聯。