美文网首页
将ViewGroup裁切成圆形

将ViewGroup裁切成圆形

作者: feifei_fly | 来源:发表于2019-09-28 10:23 被阅读0次

最近收到一个开发悬浮球的需求,悬浮球上显示一张图片,一个TextView文本。这个悬浮球被裁切成圆形。

该如何实现呢?如果利用glide 或者SimpleDraweeView单独将图片裁切成圆形,只能将imageView裁切成圆形,其他元素也需要单独裁切,不是一种优雅的处理方式。

于是想到:
定制一个ViewGroup将这个ViewGroup裁切成圆形,ViewGroup内的元素也会被裁切成圆形,不就解决了吗?

效果如下图右下角的圆形悬浮球


image.png

目标:将ViewGroup进行圆形裁切

一、方案一

利用clipPath()将ViewGroup的canvas 裁成圆形

class RoundView(context:Context,attr: AttributeSet?):RelativeLayout(context,attr) {
    private val mPath: Path = Path()
    var mRectF = RectF()

    init {
        Log.d("feifei","-----setLayerType----")
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
    }

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

    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        Log.d("feifei","onLayout changed:"+changed+",left:"+l+",top:"+t+",right:"+r+",bottom:"+b)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        Log.d("feifei","onSizeChanged:"+w+",h:"+h)
        mPath.reset();
        mRectF.set(0F, 0F, w.toFloat(), h.toFloat());
        mPath.addRoundRect(mRectF, w.toFloat()/2.0F, h.toFloat()/2.0F, Path.Direction.CW);
    }



    override fun dispatchDraw(canvas: Canvas?) {

        tryDrawCircleWithClipPath(canvas)

    }


    /**
     * 通过ClipPath的方式绘制遮罩
     */
    fun tryDrawCircleWithClipPath(canvas: Canvas?){
        canvas?.save()
        Log.d("feifei","dispatchDraw:")
        canvas?.clipPath(mPath)
        super.dispatchDraw(canvas)
        canvas?.restore()
    }


    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        Log.d("feifei","onDraw:")
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        Log.d("feifei","onMeasure:")
    }

}

使用方式:

 <com.sogou.teemo.busi_camera.menu.suspendball.RoundView
        android:id="@+id/rl_RoundView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        >

        <ImageView
            android:id="@+id/iv_logo"
            android:layout_width="@dimen/px80"
            android:layout_height="@dimen/px80"
            android:scaleType="centerCrop"
            android:src="@drawable/camera_menu_default"
            android:layout_centerHorizontal="true"
            />
        <TextView
            android:id="@+id/tv_pic_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            android:text="20张"
            android:textSize="@dimen/px18"
            android:gravity="center_horizontal"
            android:background="@color/color_99000000"
            android:layout_alignBottom="@+id/iv_logo"
            android:layout_centerHorizontal="true"
            android:layout_alignRight="@+id/iv_logo"
            android:layout_alignLeft="@+id/iv_logo"
            android:paddingBottom="@dimen/px3"
            />
    </com.sogou.teemo.busi_camera.menu.suspendball.RoundView>

方案一 非常方便的实现了圆形ViewGroup,可以将ViewGroup内的所有元素 都裁切成圆形

但是这个方案有一个缺点。

产品设计上RoundView 还要有一个旋转的动画,方案一产生的圆形ViewGroup 再旋转时会导致canvas?.clipPath(mPath)的圆心 和旋转的圆形不重合。旋转过程圆形失效。

于是尝试另一种方案Xfermode

方案二 利用Xfermode 实现


class RoundView(context:Context,attr: AttributeSet?):RelativeLayout(context,attr) {
    private val mPath: Path = Path()
    var mRectF = RectF()

    init {
        Log.d("feifei","-----setLayerType----")
        setLayerType(View.LAYER_TYPE_SOFTWARE, null)
    }

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

    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        Log.d("feifei","onLayout changed:"+changed+",left:"+l+",top:"+t+",right:"+r+",bottom:"+b)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        Log.d("feifei","onSizeChanged:"+w+",h:"+h)
        mPath.reset();
        mRectF.set(0F, 0F, w.toFloat(), h.toFloat());
        mPath.addRoundRect(mRectF, w.toFloat()/2.0F, h.toFloat()/2.0F, Path.Direction.CW);
    }



    override fun dispatchDraw(canvas: Canvas?) {

//        tryClipPath(canvas)

        tryDrawCircleShade(canvas)
    }


    /**
     * 通过ClipPath的方式绘制遮罩
     */
    fun tryClipPath(canvas: Canvas?){
        canvas?.save()
        Log.d("feifei","dispatchDraw:")
        canvas?.clipPath(mPath)
        super.dispatchDraw(canvas)
        canvas?.restore()
    }


    //外边框 白边宽度
    var mBorderWidth = 2.0F

    /**
     * 绘制圆形遮罩
     */
    fun tryDrawCircleShade(canvas: Canvas?){

        //创建新的图层
        val sc = canvas?.saveLayer(0F, 0F, canvas.getWidth().toFloat(), canvas.getHeight().toFloat(), null, Canvas.ALL_SAVE_FLAG)
        super.dispatchDraw(canvas)

        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.setAntiAlias(true)
        paint.setColor(Color.WHITE)
        paint.setStyle(Paint.Style.FILL)



        val mask = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
        val mNewCanvas = Canvas(mask)
        mNewCanvas?.drawRoundRect(mRectF, measuredWidth/2.0f, measuredHeight/2.0f, paint)

        paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.DST_IN))// 设置Xfermode模式
        canvas?.drawBitmap(mask, 0F, 0F, paint)
        paint?.setXfermode(null)


        //绘制外层的圆圈
        drawBorderCircle(canvas,paint)
        //将新建图层内容绘制到 原有图层
        canvas?.restoreToCount(sc!!)

    }



    fun drawBorderCircle(canvas: Canvas?,paint: Paint){
        //画白色圆边
        paint.setColor(Color.WHITE)
        paint.setStyle(Paint.Style.STROKE)
        paint.strokeWidth = mBorderWidth
        canvas?.drawCircle(measuredWidth/2.0f,measuredHeight/2.0f,((measuredWidth-mBorderWidth)/2.0F).toFloat(),paint)
        paint.setStyle(Paint.Style.FILL)
    }

    fun tryDST_IN_1(canvas: Canvas?){

        val sc = canvas?.saveLayer(0F, 0F, canvas.getWidth().toFloat(), canvas.getHeight().toFloat(), null, Canvas.ALL_SAVE_FLAG)

        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.setAntiAlias(true)
        paint.setColor(Color.BLUE)
        paint.setStyle(Paint.Style.FILL)

        canvas?.drawRoundRect(mRectF, measuredWidth/2.0f, measuredHeight/2.0f, paint)

        paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))// 设置Xfermode模式
        super.dispatchDraw(canvas)



//        canvas?.drawBitmap(createMask(paint), 0F, 0F, paint)
        canvas?.restoreToCount(sc!!)
        paint?.setXfermode(null)
    }

    private fun createMask(paint:Paint): Bitmap {
        val maskWidth = measuredWidth
        val maskHeight = measuredHeight
        val mask = Bitmap.createBitmap(maskWidth, maskHeight, Bitmap.Config.ARGB_8888)
        val mNewCanvas = Canvas(mask)
        mNewCanvas?.drawRoundRect(mRectF, measuredWidth/2.0f, measuredHeight/2.0f, paint)
        return mask
    }

    /**
     * 生成圆形Bitmap
     * @return
     */
    private fun makeCircle(): Bitmap {
        val bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888)
        val canvas = Canvas(bitmap)
        val paint = Paint(Paint.ANTI_ALIAS_FLAG)
        paint.color = Color.BLUE
        paint.style = Paint.Style.FILL
        val radius = measuredHeight / 2
        canvas.drawCircle(measuredHeight.toFloat() / 2, measuredHeight.toFloat() / 2, radius.toFloat(), paint)
        return bitmap
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        Log.d("feifei","onDraw:")
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        Log.d("feifei","onMeasure:")
    }

}

使用方式:

 <com.sogou.teemo.busi_camera.menu.suspendball.RoundView
        android:id="@+id/rl_RoundView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        >

        <ImageView
            android:id="@+id/iv_logo"
            android:layout_width="@dimen/px80"
            android:layout_height="@dimen/px80"
            android:scaleType="centerCrop"
            android:src="@drawable/camera_menu_default"
            android:layout_centerHorizontal="true"
            />
        <TextView
            android:id="@+id/tv_pic_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            android:text="20张"
            android:textSize="@dimen/px18"
            android:gravity="center_horizontal"
            android:background="@color/color_99000000"
            android:layout_alignBottom="@+id/iv_logo"
            android:layout_centerHorizontal="true"
            android:layout_alignRight="@+id/iv_logo"
            android:layout_alignLeft="@+id/iv_logo"
            android:paddingBottom="@dimen/px3"
            />
    </com.sogou.teemo.busi_camera.menu.suspendball.RoundView>

Xfermode 裁切图片原理 Xfermode

非常完美的实现了 圆形ViewGroup的裁切。

相关文章

网友评论

      本文标题:将ViewGroup裁切成圆形

      本文链接:https://www.haomeiwen.com/subject/xnvzuctx.html