Kotlin 背景圓頭像圖

aabbcc又一年發表於2021-01-02

1.需求:大部分的頭像選擇更換時都採取這樣的功能,半透明的蒙版,中間一個正方形的選取框,底圖隨意移動縮放,確定後擷取為背景頭像。網易雲,微信都是如此
2.實現:
1:程式碼:

package sss

import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import com.example.sosrcpro.R


class CyclerView:View{
    //圖片來源
    private var bitmap:Bitmap? = null
    private var paint:Paint? = null
    private var bitmapShader:BitmapShader? = null
    private var bitmapPaint:Paint? = null
    private var bitmatLeft = 0
    private var bitmapTop = 0
    private  var roundPaint:Paint? =null
    private var viewWidthHalf = 0F
    private var viewHeihtHalf = 0F
    var starX = 0F
    var starY = 0F
    var roundRadiu = 0F
    var detector:ScaleGestureDetector? = null
    var detectorListener:ScaleGestureDetector.OnScaleGestureListener? = null
    var fixedRadiu = 0F
    constructor(context:Context):this(context,null){

    }
    constructor(context: Context,attributeSet: AttributeSet?):this(context,attributeSet,0){

    }
    constructor(context: Context,attributeSet: AttributeSet?,style:Int):super(context,attributeSet,style){
        paint = Paint()
        bitmapPaint = Paint()
        roundPaint = Paint()
        paint?.apply {
            setAntiAlias(true)
        }
        bitmap = BitmapFactory.decodeResource(resources, R.mipmap.timg)
        detectorListener = object:ScaleGestureDetector.OnScaleGestureListener{

            //返回true才能進入onScale
            override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {

                return  true
            }

            override fun onScaleEnd(detector: ScaleGestureDetector?) {

            }

            override fun onScale(detector: ScaleGestureDetector): Boolean {
                var currentS = detector.currentSpan
                var currentX = detector.currentSpanX
                var currentY = detector.currentSpanY
                var currentScale = detector.scaleFactor
                Log.d("save","currentS:"+currentS.toString())
                Log.d("save","currentScale:"+currentScale.toString())
                if(currentScale>1)
                scaleBitmapByTouch(1.1F)
                else if(currentScale<1){
                    scaleBitmapByTouch(0.9F)
                }
                return true
            }
        }
        detector = ScaleGestureDetector(getContext(),detectorListener)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        viewWidthHalf = measuredWidth/2F
        viewHeihtHalf = measuredHeight/2F
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        var srcrect =  Rect(0, 0, bitmap!!.getWidth(), bitmap!!.getHeight())
        //單指移動圖片
        var dscrect =  Rect(bitmatLeft, bitmapTop, bitmap!!.getWidth()+bitmatLeft, bitmap!!.getHeight()+bitmapTop)
        //繪製底圖
        canvas?.drawBitmap(bitmap!!, srcrect, dscrect, bitmapPaint)
        paint?.setColor(Color.parseColor("#80666666"))
        var radiu = canvas?.width!! /3F
        fixedRadiu = radiu/2F
       var layerId =  canvas.saveLayer(0F,0F,canvas.width*1F,canvas.height*1F,null,Canvas.ALL_SAVE_FLAG)
        //繪製灰色半透明的蒙版
        canvas?.drawRect(0F,0F,canvas.width*1F,canvas.height*1F,paint!!)
        //DST_OUT:取下層繪製非交集部分。不顯示上層(由於圓圈部分存在交集,不顯示這下層部分,就鏤空了,
        // 進而顯示底圖,而底圖其他部分顯示其他非交集下層部分)
        paint?.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_OUT))
        paint?.setColor(Color.BLUE)
        //畫圓,通過透底得到鏤空的底圖
        canvas?.drawCircle(canvas.width/2F,canvas.height/2F,fixedRadiu,paint!!)
        paint?.setXfermode(null)
        canvas?.restoreToCount(layerId)
    }
    fun setBitmap(bitmap:Bitmap){
        this.bitmap = bitmap
        invalidate()
    }
    fun getBitmapFormView():Bitmap{

        return bitmap!!
    }
    fun scaleBitmapByTouch(scale:Float){
        var matrix = Matrix()
        matrix.setScale(scale,scale)
        bitmap = Bitmap.createBitmap(bitmap!!,0,0,bitmap!!.width,bitmap!!.height,matrix,true)

    }

    fun  getCircleBitmap( bitmap:Bitmap) {
        var circleBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        var canvas =  Canvas(circleBitmap)
        var paint =  Paint()
        var rect =  Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())
        var rectF =  RectF(rect);
        var roundRa = 0.0f
        if (bitmap.getWidth() > bitmap.getHeight()) {
            roundRa = bitmap.getHeight() / 2.0f
        } else {
            roundRa = bitmap.getWidth() / 2.0f
        }
    }
   //是頻繁呼叫的,starX,starY的初始化要放在類中,不是在函式中,否則不管是任何觸控型別都設定為0了。
    //加上放大縮小,範圍控制,截圖,儲存。
    override fun onTouchEvent(event: MotionEvent?): Boolean {
       detector!!.onTouchEvent(event)
        var touchType = event!!.actionMasked
        when(touchType){
            //單指,多指
            MotionEvent.ACTION_DOWN->{
                starX =event.x
                starY = event.y
                Log.e("native","ACTION_DOWN")
            }
            //單指,多指
            MotionEvent.ACTION_MOVE->{
                var x =event.x
                var y = event.y
                var xxx = x-starX
                var yyy =y-starY
                starX =x
                starY = y
                setBitmapPosition(bitmatLeft+xxx,bitmapTop+yyy)
                Log.e("native","ACTION_MOVE")
                var pointCount = event.pointerCount
                if(pointCount==2){
                }
            }
            //單指,多指
            MotionEvent.ACTION_UP->{
                Log.e("native","ACTION_UP")
            }
            //單指多指都是ACTION_MOVE
            //多指
            MotionEvent.ACTION_POINTER_DOWN->{
                Log.e("native","ACTION_POINTER_DOWN")
            }
            //多指
            MotionEvent.ACTION_POINTER_UP->{
                Log.e("native","ACTION_POINTER_UP")
            }
        }
        return true
    }
    fun setBitmapPosition(left:Float,top:Float){
        bitmatLeft = left.toInt()
        bitmapTop = top.toInt()
        invalidate()
    }
    fun getRoundBitmap(radiu:Float):Bitmap{
        //畫布設定為邊長為圓框的半徑
        var output = Bitmap.createBitmap(fixedRadiu.toInt()*2,fixedRadiu.toInt()*2,Bitmap.Config.ARGB_8888)
         var canvas = Canvas(output)
        //原圖
        var srcRect = Rect(0,0,bitmap!!.width,bitmap!!.height)
        //移動,放置的位置又這個確定,看到的圓框與新的左上角的圓要效果一樣。相當於多移動一部分(即兩個圓的距離)
        var dscRect = Rect(bitmatLeft-(viewWidthHalf.toInt()-fixedRadiu.toInt()),bitmapTop-(viewHeihtHalf.toInt()-fixedRadiu.toInt()),bitmap!!.width+bitmatLeft-(viewWidthHalf.toInt()-fixedRadiu.toInt()),bitmap!!.height+bitmapTop-(viewHeihtHalf.toInt()-fixedRadiu.toInt()))
        //PorterDuff.Mode.SRC_IN 後不管背景顏色如何,我這都是灰色的。
        canvas.drawARGB(0,255,0,0)
        roundPaint!!.color = Color.parseColor("#ff424242")
        //圓的中心座標不變,移動圖片進行繪製即可。
        canvas.drawCircle(fixedRadiu,fixedRadiu,fixedRadiu,roundPaint!!)
        roundPaint!!.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
        canvas.drawBitmap(bitmap!!,srcRect,dscRect,roundPaint)
        roundPaint!!.setXfermode(null)     //每次繪製完畢後都應重置為null
        //二次擷取

       return  output
    }
    fun saveBitmap():Bitmap{
         return getRoundBitmap(fixedRadiu)
    }

}

2效果:
在這裡插入圖片描述
在這裡插入圖片描述
這裡的縮放還沒有限制,單指移動也還沒限制,懶得弄了,這只是練練手,懂個思路就行,其實大概也能用了,哈哈哈哈哈哈哈哈哈。

程式碼改了,效果圖沒改,程式碼改後截圖大小剛好和圓框大小一致。原來的是和原圖大小一樣,這樣就沒辦法直接用了。

相關文章