使用ClipPath实现动态异形切割动效

132 阅读1分钟

项目中有一个需求需要完成跟手异形切割动效,大概的效果如下

整体思路:由于圆角和路径效果非常规,无法使用outline方法实现,于是使用Path勾勒出切割区域,然后在dispatchDraw方法中切割canvas.clipPath指定绘制的区域,同时根据手势位置动态更新路径的参数,不断循环路径更新+重绘,从而实现跟手的异性切割效果。

1.路径点绘制顺序

2.使用代码勾勒路径

使用Path.moveTo/lineTo/arcTo等方法勾勒出路径

class XXLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {    
    private var clipTop: Int = 0    private var clipEnd: Int = 0
    private var clipBottom: Int = 0
    private var clipCornerRadius = xx
    private val leftOrRightMargin = xx
    ...
    init {
        val defaultDisplayContext = DisplayUtil.getDefaultDisplayContext(context) ?: context
        val defaultTypedArray =
            defaultDisplayContext.obtainStyledAttributes(attrs, R.styleable.xx)
        clipRadius =
            defaultTypedArray.getDimensionPixelSize(R.styleable.xx, 0)
        clipStart =
            defaultTypedArray.getDimensionPixelSize(R.styleable.xx, 0)
        clipEnd =
            defaultTypedArray.getDimensionPixelSize(R.styleable.xx, 0)

        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.xx)
        clipTop = typedArray.getDimensionPixelSize(R.styleable.xx, 0)

        defaultTypedArray.recycle()
        typedArray.recycle()
    }
    ...
    private fun updateClipPath() {
        val clipStart = clipStart
        val clipEnd = width - clipEnd
        mPath.rewind()
        mPathRect.rewind()
        val clipRectMarginTest = 0f//方便调试
        //外切割矩形参数
        val clipRectLeft = clipStart + clipRectMarginTest
        val clipRectTop = clipTop.toFloat()
        val clipRectRight = clipEnd - clipRectMarginTest
        val clipRectBottom = clipBottom.toFloat()
        val clipRectBottomInner = clipRectBottom - clipCornerRadius
        //内切割矩形参数,卡片列表真实区域
        val clipCornerLeft = leftOrRightMargin
        val clipCornerRight = width - leftOrRightMargin
        val clipCornerBottom = clipRectBottom

        mPath.moveTo(clipRectLeft, clipRectTop)//路径点1
        mPath.lineTo(clipRectLeft, clipRectBottomInner)//路径点2
        mPath.lineTo(clipCornerLeft, clipRectBottomInner)//路径点3

        //左下圆角
        mPath.arcTo(
            RectF(
                clipCornerLeft,
                clipCornerBottom - clipCornerRadius * 2,
                clipCornerLeft + clipCornerRadius * 2,
                clipCornerBottom
            ), ROUNDRECT_LEFT_START_ANGEL, ROUNDRECT_LEFT_END_ANGEL, false
        )
        mPath.lineTo(clipCornerRight - clipCornerRadius, clipRectBottom)//路径点5
        //右下圆角
        mPath.arcTo(
            RectF(
                clipCornerRight - clipCornerRadius * 2,
                clipCornerBottom - clipCornerRadius * 2,
                clipCornerRight,
                clipCornerBottom
            ), ROUNDRECT_RIGHT_START_ANGEL, ROUNDRECT_RIGHT_END_ANGEL, false
        )
        mPath.lineTo(clipRectRight, clipRectBottomInner)//路径点7
        mPath.lineTo(clipRectRight, clipRectTop)//路径点8
        mPath.lineTo(clipRectLeft, clipRectTop)//路径点1
    }
    ...
}

3.限制绘制区域实现切割效果

   override fun dispatchDraw(canvas: Canvas) {
        if (enableClipPath && clipRegionHasSet) {
            updateClipPath()
            canvas?.clipPath(mPath)//将canvas裁剪到path设定的区域,往后的绘制都只能在此区域中
        }
        super.dispatchDraw(canvas)
    }