我报名参加金石计划1期应战——瓜分10万奖池,这是我的第3篇文章,点击检查活动概况

定制圆角与布景的自界说ViewGroup结束

前言

现在线上的一些第三方圆角容器大部分都只支撑四周固定圆角,咱们一些运用场景只需求顶部圆角,或许底部圆角,或许一个角圆角。

(话说为什么咱们的UI这么喜欢各种奇葩圆角,想哭。。。)

对于这些定制化的圆角需求,咱们怎么自界说结束呢?又有哪些结束办法呢?

之前咱们讲过圆角图片的自界说,那么和咱们的自界说圆角容器又有哪些区别呢?

带着这些问题,咱们一步一步往下看。

技术选型

可能有同学问了,用shape画一个圆角不就行了吗?各种的圆角都能画,实在不行还能够找UI要各种圆角的切图。有必要用自界说ViewGroup来结束吗?

确实在一部分场景中咱们能够经过这样设置圆角布景的办法来解决问题,一般设计都有内距离,咱们设置了布景,然后再经过设置距离来确保内部的控件不会和父容器交叉重叠。

由于这样设置的布景仅仅欺骗了视觉,并没有裁剪控件,假如在特定的场景下,如挨着角落布局,或许翻滚起来的时分,就会发现内部的控件’超越’了父容器的规模。

一句话说不清楚,咱们看下面这张图应该能理解:

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

我运用自界说的 FrameLayout 设置异性圆角,而且设置异性圆角的图片布景,然后内部增加一个子View,那么子View就不会超越左上角的圆角规模。

假如在这样的特别场景下,要达到这样的作用,咱们就需求自界说View的办法来裁剪父容器,让它实在的就是那样的形状!

在之前咱们结束的圆角ImageView的过程中,咱们了解到裁剪View的几种办法。

一共有 ClipPath Xfermode Shader 另外还有一种 Outline 的办法。

之前咱们的图片裁剪是利用 Shader 来结束的。现在咱们裁剪ViewGroup咱们最便利的办法是 Outline 可是咱们需求对一些 Outline 结束不了的版别和作用,咱们运用 Shader 做一些兼容处理即可。

需求收拾

首先在着手之前咱们理清一下思路,咱们需求哪些功用,以及怎么结束。

  1. 经过战略形式来办理不同版别的裁剪结束
  2. 经过一个接口来封装逻辑办理这些战略
  3. 经过结束不同的自界说View来办理接口结束类间接的经过不同的战略来裁剪
  4. 运用自界说特点来动态的装备需求的特点
  5. 裁剪结束之后需求接收体系的布景的制作,由自己结束
  6. 运用Shader的办法制作布景
  7. 对原生布景特点的兼容处理

阐明:

根据不同的版别和需求,运用不同的战略来裁剪 ViewGroup,需求考虑到不同的圆角,一致的圆角和圆形的裁剪。

裁剪结束之后在部分计划中咱们设置布景仍是会覆盖到已裁剪的区域,这时分咱们一致处理布景的制作。

由于体系 View 自带布景的设置,和咱们的布景制作有冲突,咱们需求接收体系的 View 的布景制作,而且需求处理 Xml 中设置布景与 Java 代码中设置布景的兼容性问题。

终究运用 Shader 的办法制作各种形状的布景制作。需求留意处理不同的圆角,圆角和圆形的制作办法。

整体框架的大致构建图如下:

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

下面跟着我一步一步的来结束吧。

运用战略形式兼容裁剪的办法

其实市面上大部分的裁剪都是运用的 Outline 的办法,这是一种极好的计划。我也是运用这种计划,那我为什么不直接运用第三方库算了。。。 就是由于兼容性问题和一些功用性问题不能解决。

Outline能够制作圆形和一致圆角,可是它无法设置异形的圆角。而且它只能在5.0以上的体系才能运用。所以咱们需求对异形的圆角和低版别做兼容处理。

中心代码如下:

 private fun init(view: View, context: Context, attributeSet: AttributeSet?, attrs: IntArray, attrIndexs: IntArray) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            //5.0版别以上与5.0一下的兼容处理
            //判别是否包含自界说圆角
            val typedArray = context.obtainStyledAttributes(attributeSet, attrs)
            val topLeft = typedArray.getDimensionPixelOffset(attrIndexs[2], 0).toFloat()
            val topRight = typedArray.getDimensionPixelOffset(attrIndexs[3], 0).toFloat()
            val bottomLeft = typedArray.getDimensionPixelOffset(attrIndexs[4], 0).toFloat()
            val bottomRight = typedArray.getDimensionPixelOffset(attrIndexs[5], 0).toFloat()
            typedArray.recycle()
            roundCirclePolicy = if (topLeft > 0 || topRight > 0 || bottomLeft > 0 || bottomRight > 0) {
                //自界说圆角运用兼容计划
                RoundCircleLayoutShaderPolicy(view, context, attributeSet, attrs, attrIndexs)
            } else {
                //运用OutLine裁剪计划
                RoundCircleLayoutOutlinePolicy(view, context, attributeSet, attrs, attrIndexs)
            }
        } else {
            // 5.0以下的版别运用兼容计划
            roundCirclePolicy = RoundCircleLayoutShaderPolicy(view, context, attributeSet, attrs, attrIndexs)
        }
    }

咱们需求对5.0一下的版别运用 clipPath 的计划裁剪,5.0以上的计划结束 Outline的计划裁剪。

Outline的裁剪:


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    override fun beforeDispatchDraw(canvas: Canvas?) {
        //5.0版别以上,选用ViewOutlineProvider来裁剪view
        mContainer.clipToOutline = true
    }
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    override fun afterDispatchDraw(canvas: Canvas?) {
        //5.0版别以上,选用ViewOutlineProvider来裁剪view
        mContainer.outlineProvider = object : ViewOutlineProvider() {
            override fun getOutline(view: View, outline: Outline) {
                if (isCircleType) {
                    //假如是圆形裁剪圆形
                    val bounds = Rect()
                    calculateBounds().roundOut(bounds)
                    outline.setRoundRect(bounds, bounds.width() / 2.0f)
//                    outline.setOval(0, 0, mContainer.width, mContainer.height);  //两种办法都能够
                } else {
                    //假如是圆角-裁剪圆角
                    if (mTopLeft > 0 || mTopRight > 0 || mBottomLeft > 0 || mBottomRight > 0) {
                        //假如是独自的圆角
                        val path = Path()
                        path.addRoundRect(
                            calculateBounds(),
                            floatArrayOf(mTopLeft, mTopLeft, mTopRight, mTopRight, mBottomRight, mBottomRight, mBottomLeft, mBottomLeft),
                            Path.Direction.CCW
                        )
                        //不支撑2阶的曲线
                        outline.setConvexPath(path)
                    } else {
                        //假如是一致圆角
                        outline.setRoundRect(0, 0, mContainer.width, mContainer.height, mRoundRadius)
                    }
                }
            }
        }
    }

clipPath 计划的中心代码

    override fun beforeDispatchDraw(canvas: Canvas?) {
        canvas?.clipPath(mPath)
    }
    override fun afterDispatchDraw(canvas: Canvas?) {
    }
    //裁剪的途径
    private fun setupRoundPath() {
        mPath.reset()
        if (isCircleType) {
            mPath.addOval(0f, 0f, mContainer.width.toFloat(), mContainer.height.toFloat(), Path.Direction.CCW)
        } else {
            //假如是圆角-裁剪圆角
            if (mTopLeft > 0 || mTopRight > 0 || mBottomLeft > 0 || mBottomRight > 0) {
                mPath.addRoundRect(
                    calculateBounds(),
                    floatArrayOf(mTopLeft, mTopLeft, mTopRight, mTopRight, mBottomRight, mBottomRight, mBottomLeft, mBottomLeft),
                    Path.Direction.CCW
                )
            } else {
                mPath.addRoundRect(mDrawableRect, mRoundRadius, mRoundRadius, Path.Direction.CCW)
            }
        }
    }

其间调用的时机咱们经过战略的接口,界说的一系列的钩子函数。

// 战略的接口界说
interface IRoundCirclePolicy {
    fun beforeDispatchDraw(canvas: Canvas?)
    fun afterDispatchDraw(canvas: Canvas?)
    fun onDraw(canvas: Canvas?): Boolean
    fun onLayout(left: Int, top: Int, right: Int, bottom: Int)
}

RoundCircleViewImpl:

    fun beforeDispatchDraw(canvas: Canvas?) {
        roundCirclePolicy.beforeDispatchDraw(canvas)
    }
    fun afterDispatchDraw(canvas: Canvas?) {
        roundCirclePolicy.afterDispatchDraw(canvas)
    }
    fun onDraw(canvas: Canvas?): Boolean {
        return roundCirclePolicy.onDraw(canvas)
    }

终究在详细的ViewGroup中结束。

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        roundCircleViewImpl.onLayout(changed, left, top, right, bottom)
    }
    override fun dispatchDraw(canvas: Canvas?) {
        roundCircleViewImpl.beforeDispatchDraw(canvas)
        super.dispatchDraw(canvas)
        roundCircleViewImpl.afterDispatchDraw(canvas)
    }
    override fun onDraw(canvas: Canvas?) {
        if (roundCircleViewImpl.onDraw(canvas)) {
            super.onDraw(canvas)
        }
    }

在制作,制作前,制作后咱们都有对应的拦截和结束。经过上面的裁剪中心代码咱们就能结束不同功用不同版别的详细战略结束。

到此咱们就能裁剪ViewGroup结束,而且能裁剪到指定的形状。

抽取自界说特点装备

这儿咱们把常用的自界说特点抽取出来,然后再咱们笼统的战略类中拿到对应的特点,取出设置的一些值,然后再详细的战略结束类中就能够操作运用了。

自界说特点界说如下:

    <attr name="is_circle" format="boolean" />
    <attr name="round_radius" format="dimension" />
    <attr name="topLeft" format="dimension" />
    <attr name="topRight" format="dimension" />
    <attr name="bottomLeft" format="dimension" />
    <attr name="bottomRight" format="dimension" />
    <attr name="round_circle_background_color" format="color" />
    <attr name="round_circle_background_drawable" format="reference" />
    <attr name="is_bg_center_crop" format="boolean" />
    <declare-styleable name="RoundCircleConstraintLayout">
        <attr name="is_circle" />
        <attr name="round_radius" />
        <attr name="topLeft" />
        <attr name="topRight" />
        <attr name="bottomLeft" />
        <attr name="bottomRight" />
        <attr name="round_circle_background_color" />
        <attr name="round_circle_background_drawable" />
        <attr name="is_bg_center_crop" />
    </declare-styleable>
   ...

在详细的ViewGroup中咱们把特点封装到目标中,终究传递给战略类去取出来结束

    private fun init(view: View, context: Context, attributeSet: AttributeSet?) {
        roundCircleViewImpl = RoundCircleViewImpl(
            view,
            context,
            attributeSet,
            R.styleable.RoundCircleNestedScrollView,
            intArrayOf(
                R.styleable.RoundCircleNestedScrollView_is_circle,
                R.styleable.RoundCircleNestedScrollView_round_radius,
                R.styleable.RoundCircleNestedScrollView_topLeft,
                R.styleable.RoundCircleNestedScrollView_topRight,
                R.styleable.RoundCircleNestedScrollView_bottomLeft,
                R.styleable.RoundCircleNestedScrollView_bottomRight,
                R.styleable.RoundCircleNestedScrollView_round_circle_background_color,
                R.styleable.RoundCircleNestedScrollView_round_circle_background_drawable,
                R.styleable.RoundCircleNestedScrollView_is_bg_center_crop,
            )
        )
        nativeBgDrawable?.let {
            roundCircleViewImpl.setNativeDrawable(it)
        }
    }

这儿结束了 roundCircleViewImpl 目标, roundCircleViewImpl 目标内部又持有战略的目标,咱们就能够在战略类中拿到特点。

internal abstract class AbsRoundCirclePolicy(
    view: View,
    context: Context,
    attributeSet: AttributeSet?,
    attrs: IntArray,
    attrIndex: IntArray
) : IRoundCirclePolicy {
    var isCircleType = false
    var mRoundRadius = 0f
    var mTopLeft = 0f
    var mTopRight = 0f
    var mBottomLeft = 0f
    var mBottomRight = 0f
    var mRoundBackgroundDrawable: Drawable? = null
    var mRoundBackgroundBitmap: Bitmap? = null
    var isBGCenterCrop = true;
    val mContainer: View = view
    init {
        initialize(context, attributeSet, attrs, attrIndex)
    }
    private fun initialize(context: Context, attributeSet: AttributeSet?, attrs: IntArray, attrIndexs: IntArray) {
        val typedArray = context.obtainStyledAttributes(attributeSet, attrs)
        isCircleType = typedArray.getBoolean(attrIndexs[0], false)
        mRoundRadius = typedArray.getDimensionPixelOffset(attrIndexs[1], 0).toFloat()
        mTopLeft = typedArray.getDimensionPixelOffset(attrIndexs[2], 0).toFloat()
        mTopRight = typedArray.getDimensionPixelOffset(attrIndexs[3], 0).toFloat()
        mBottomLeft = typedArray.getDimensionPixelOffset(attrIndexs[4], 0).toFloat()
        mBottomRight = typedArray.getDimensionPixelOffset(attrIndexs[5], 0).toFloat()
        val roundBackgroundColor = typedArray.getColor(attrIndexs[6], Color.TRANSPARENT)
        mRoundBackgroundDrawable = ColorDrawable(roundBackgroundColor)
        mRoundBackgroundBitmap = getBitmapFromDrawable(mRoundBackgroundDrawable)
        if (typedArray.hasValue(attrIndexs[7])) {
            mRoundBackgroundDrawable = typedArray.getDrawable(attrIndexs[7])
            mRoundBackgroundBitmap = getBitmapFromDrawable(mRoundBackgroundDrawable)
        }
        isBGCenterCrop = typedArray.getBoolean(attrIndexs[8], true)
        typedArray.recycle()
    }

笼统的战略类拿到了特点值之后,在详细的战略裁剪类中咱们就能够运用这些界说的特点了。

圆角布景的处理

咱们在自界说特点中设置了布景的特点,颜色和图片的布景,此时咱们需求拿到这些Bitmap去制作出来。

制作的代码咱们之前在RoundImageView中有详细的讲过,经过BitmapShader的办法制作。

    private fun initViewData() {
        mContainer.setWillNotDraw(false)
        mDrawableRect = RectF()
        mPath = Path()
        mBitmapPaint = Paint()
        mShaderMatrix = Matrix()
    }
    //设置画笔和BitmapShader等
    private fun setupBG() {
        mDrawableRect.set(calculateBounds())
        if (mRoundBackgroundDrawable != null && mRoundBackgroundBitmap != null) {
            mBitmapWidth = mRoundBackgroundBitmap!!.width
            mBitmapHeight = mRoundBackgroundBitmap!!.height
            mBitmapShader = BitmapShader(mRoundBackgroundBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
            if (mRoundBackgroundBitmap!!.width != 2) {
                updateShaderMatrix()
            }
            mBitmapPaint.isAntiAlias = true
            mBitmapPaint.shader = mBitmapShader
        }
    }

需求留意的是ViewGroup默许是不走 onDraw 办法的,咱们经过 setWillNotDraw(false) 的办法,答应ViewGroup能制作。

然后咱们在onDraw的钩子函数中运用Canves来制作

 override fun onDraw(canvas: Canvas?): Boolean {
        if (isCircleType) {
            canvas?.drawCircle(
                mDrawableRect.centerX(), mDrawableRect.centerY(),
                Math.min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f), mBitmapPaint
            )
        } else {
            if (mTopLeft > 0 || mTopRight > 0 || mBottomLeft > 0 || mBottomRight > 0) {
                //运用独自的圆角
                val path = Path()
                path.addRoundRect(
                    mDrawableRect, floatArrayOf(mTopLeft, mTopLeft, mTopRight, mTopRight, mBottomRight, mBottomRight, mBottomLeft, mBottomLeft),
                    Path.Direction.CW
                )
                canvas?.drawPath(path, mBitmapPaint)
            } else {
                //运用一致的圆角
                canvas?.drawRoundRect(mDrawableRect, mRoundRadius, mRoundRadius, mBitmapPaint)
            }
        }
        //是否需求super再制作
        return true
    }

这儿需求留意的是,在咱们设置 BitmapShader 的 Matrix 时分,咱们需求设置缩放,这时分设置的图片布景是从左上角开端的,并没有居中。

所以咱们需求自界说特点来装备,是否需求布景图片居中展示,默许让布景图片居中显现,中心代码如下:

    private fun updateShaderMatrix() {
        var scale = 1.0f
        var dx = 0f
        var dy = 0f
        mShaderMatrix.set(null)
        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
            scale = mDrawableRect.height() / mBitmapHeight.toFloat()
            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f
        } else {
            scale = mDrawableRect.width() / mBitmapWidth.toFloat()
            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f
        }
        mShaderMatrix.setScale(scale, scale)
        if (isBGCenterCrop) {
            mShaderMatrix.postTranslate((dx + 0.5f).toInt() + mDrawableRect.left, (dy + 0.5f).toInt() + mDrawableRect.top)
        }
        mBitmapShader?.let {
            it.setLocalMatrix(mShaderMatrix)
        }
    }

能够看一下裁剪控件和制作布景之后的作用图:

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

这些作用都是ViewGroup,不是ImageView加载的,其间图二是成心设置为布景不居中展示的作用。在图一中咱们内部增加子View就能够看到裁剪的作用与布景的作用。

原生布景特点的处理

尽管咱们简单的结束了控件的裁剪和布景的制作,可是咱们的健壮性还不够,当咱们再xml里面设置background的时分,而不运用自界说特点,就会没作用。

咱们需求接收体系View的setBackground的一些办法,让它走到咱们自界说的布景制作中来。

例如:

    <RoundCircleFrameLayout
        android:layout_width="@dimen/d_150dp"
        android:layout_height="@dimen/d_150dp"
        android:background="@color/gray"
        app:round_radius="@dimen/d_20dp"/>
    <RoundCircleConstraintLayout
        android:id="@+id/layout_2"
        android:layout_width="@dimen/d_150dp"
        android:layout_height="@dimen/d_150dp"
        android:layout_marginTop="@dimen/d_10dp"
        app:is_circle="true"
        app:round_circle_background_color="#ff00ff"
        app:round_radius="@dimen/d_40dp"/>

咱们直接设置 android:background 的时分咱们需求重写这些办法,然后取到其间的值,然后再交给战略类去详细的制作。

中心代码如下:

    private fun init(view: View, context: Context, attributeSet: AttributeSet?) {
        roundCircleViewImpl = RoundCircleViewImpl(
            view,
            context,
            attributeSet,
            R.styleable.RoundCircleFrameLayout,
            intArrayOf(
                R.styleable.RoundCircleFrameLayout_is_circle,
                R.styleable.RoundCircleFrameLayout_round_radius,
                R.styleable.RoundCircleFrameLayout_topLeft,
                R.styleable.RoundCircleFrameLayout_topRight,
                R.styleable.RoundCircleFrameLayout_bottomLeft,
                R.styleable.RoundCircleFrameLayout_bottomRight,
                R.styleable.RoundCircleFrameLayout_round_circle_background_color,
                R.styleable.RoundCircleFrameLayout_round_circle_background_drawable,
                R.styleable.RoundCircleFrameLayout_is_bg_center_crop,
            )
        )
        nativeBgDrawable?.let {
            roundCircleViewImpl.setNativeDrawable(it)
        }
    }
    private var nativeBgDrawable: Drawable? = null
    override fun setBackground(background: Drawable?) {
        if (!this::roundCircleViewImpl.isInitialized) {
            nativeBgDrawable = background
        } else {
            roundCircleViewImpl.setBackground(background)
        }
    }
    override fun setBackgroundColor(color: Int) {
        if (!this::roundCircleViewImpl.isInitialized) {
            nativeBgDrawable = ColorDrawable(color)
        } else {
            roundCircleViewImpl.setBackground(background)
        }
    }
    override fun setBackgroundResource(resid: Int) {
        if (!this::roundCircleViewImpl.isInitialized) {
            nativeBgDrawable = context.resources.getDrawable(resid)
        } else {
            roundCircleViewImpl.setBackground(background)
        }
    }
    override fun setBackgroundDrawable(background: Drawable?) {
        if (!this::roundCircleViewImpl.isInitialized) {
            nativeBgDrawable = background
        } else {
            roundCircleViewImpl.setBackground(background)
        }
    }

咱们对Java中设置的布景与xml中设置的布景独自的处理。

internal abstract class AbsRoundCirclePolicy(
    view: View,
    context: Context,
    attributeSet: AttributeSet?,
    attrs: IntArray,
    attrIndex: IntArray
) : IRoundCirclePolicy {
    var isCircleType = false
    var mRoundRadius = 0f
    var mTopLeft = 0f
    var mTopRight = 0f
    var mBottomLeft = 0f
    var mBottomRight = 0f
    var mRoundBackgroundDrawable: Drawable? = null
    var mRoundBackgroundBitmap: Bitmap? = null
    var isBGCenterCrop = true;
    val mContainer: View = view
    override fun setNativeDrawable(drawable: Drawable) {
        mRoundBackgroundDrawable = drawable
        mRoundBackgroundBitmap = getBitmapFromDrawable(mRoundBackgroundDrawable)
    }
}

在xml中设置的布景终究会调用到战略的笼统类中,赋值给Bitmap,然后咱们的战略详细结束类就会制作出布景。

而Java中的手动设置布景则会走到咱们战略接口界说的办法中

// 战略的接口界说
interface IRoundCirclePolicy {
    fun isCustomRound(): Boolean
    fun beforeDispatchDraw(canvas: Canvas?)
    fun afterDispatchDraw(canvas: Canvas?)
    fun onDraw(canvas: Canvas?): Boolean
    fun onLayout(left: Int, top: Int, right: Int, bottom: Int)
    fun setBackground(background: Drawable?)
    fun setBackgroundColor(color: Int)
    fun setBackgroundResource(resid: Int)
    fun setBackgroundDrawable(background: Drawable?)
    fun setNativeDrawable(drawable: Drawable)
}

而它的详细结束不是由笼统战略类结束,是交给战略的详细结束类去结束,由于需求及时的改写,所以是详细结束类去结束这些办法。

中心代码如下:

    //手动设置布景的设置
    override fun setBackground(background: Drawable?) {
        setRoundBackgroundDrawable(background)
    }
    override fun setBackgroundColor(color: Int) {
        val drawable = ColorDrawable(color)
        setRoundBackgroundDrawable(drawable)
    }
    override fun setBackgroundResource(resid: Int) {
        val drawable: Drawable = mContainer.context.resources.getDrawable(resid)
        setRoundBackgroundDrawable(drawable)
    }
    override fun setBackgroundDrawable(background: Drawable?) {
        setRoundBackgroundDrawable(background)
    }
    //重新设置Drawable
    private fun setRoundBackgroundDrawable(drawable: Drawable?) {
        mRoundBackgroundDrawable = drawable
        mRoundBackgroundBitmap = getBitmapFromDrawable(mRoundBackgroundDrawable)
        setupBG()
        //重绘
        mContainer.invalidate()
    }

也是相同的赋值操作,仅仅多了手动改写的功用。

处处xml中的布景设置 和 Java 中手动设置咱们就都接收了过来自己制作了。

试试吧!

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

xml里面设置的布景能够正常显现,那咱们设置一个点击事件,换一下图片布景试试

    findViewById<ViewGroup>(R.id.layout_2).click {
        it.background = drawable(R.drawable.chengxiao)
    }

留意由于咱们接收了布景的制作,这儿咱们运用的是View原生的办法即可

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

第二张图就换成了图片布景,内部的子View也能正常的显现,也是显现在正常的位置,契合咱们的要求。

到此基本上就结束了咱们的自界说圆角ViewGroup了。可是对应一些列表与翻滚的容器咱们能不能做相同的裁剪呢?

对RecyclerView和ScrollView的支撑

除了一些常用的容器,咱们还有列表的处理,在一些场景中咱们常见一些圆角的列表,比方 RecyclerView、 ScrollView 等。

都是能够结束的,其实它们扩展起来非常的便利。咱们只需求加上对应的自界说特点,只需求修正获取自界说特点的办法,其他的办法都是相同的。

例如:

class RoundCircleScrollView : ScrollView, IRoundCircleView {
    private lateinit var roundCircleViewImpl: RoundCircleViewImpl
    constructor(context: Context) : this(context, null)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(this, context, attrs)
    }
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        init(this, context, attrs)
    }
    private fun init(view: View, context: Context, attributeSet: AttributeSet?) {
        roundCircleViewImpl = RoundCircleViewImpl(
            view,
            context,
            attributeSet,
            R.styleable.RoundCircleScrollView,
            intArrayOf(
                R.styleable.RoundCircleScrollView_is_circle,
                R.styleable.RoundCircleScrollView_round_radius,
                R.styleable.RoundCircleScrollView_topLeft,
                R.styleable.RoundCircleScrollView_topRight,
                R.styleable.RoundCircleScrollView_bottomLeft,
                R.styleable.RoundCircleScrollView_bottomRight,
                R.styleable.RoundCircleScrollView_round_circle_background_color,
                R.styleable.RoundCircleScrollView_round_circle_background_drawable,
                R.styleable.RoundCircleScrollView_is_bg_center_crop,
            )
        )
        nativeBgDrawable?.let {
            roundCircleViewImpl.setNativeDrawable(it)
        }
    }
     ...
}

运用起来也是和一般的容器是相同样的。

    <RoundCircleNestedScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:round_radius="@dimen/d_30dp">
    <RoundCircleRecyclerView
        android:id="@+id/recyclerView"
        app:topRight="@dimen/d_20dp"
        app:topLeft="@dimen/d_20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

换上Scrollview的详细作用:

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

RecyclerView是内部的Item翻滚,作用相对更好:

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

RV带上头布局与脚布局相同不影响圆角的裁剪

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景

录制GIF的时分好像录制规模有点问题,导致录制出来的GIF的圆角有一点裁剪的感觉,其实实在作用和ViewGroup是相同的作用。

怎么运用? 其实假如咱们运用Scrollview的话,最好是用一般的圆角容器包裹 RoundCircleScrollView ,这样能够达到圆角固定的作用,或许运用shape设置布景也能够,咱们能够灵敏选择。

RV由所以内部的Item翻滚就能够完美的裁剪,能够结束一些特别的圆角需求。

假如想扩展更多的ViewGroup,或许自己的自界说ViewGroup,能够直接扩展即可,界说对应的自界说特点,封装成目标给 RoundCircleViewImpl 即可。详细能够参阅源码。

到此咱们就悉数结束完毕了,哪里要弯就弯哪里,妈妈再也不必担心圆角的结束了。

总结

运用Shape圆角的布景或图片布景和运用自界说ViewGroup裁剪其实是两种不同的思路,关键是看需求,是否需求贴边的时分需求保持圆角作用。咱们按需选择即可。

关于自界说View的裁剪计划,其实上面说了有多种结束,我运用了兼容性和作用都相对比较好的两种计划 Outline 和 Shader ,当然了,假如有更好的计划也欢迎咱们一同讨论。

运用自界说ViewGroup的办法,算是解决了我开发中的一些痛点,特别是RV的一些裁剪,在一些特定的场景下很好用,我就不需求对Item做一些特别的处理。

好了本文的悉数代码与Demo都现已开源。有爱好能够看这儿。

假如想直接运用,我现已传到 Maven 仓库,咱们直接依靠即可。

implementation "com.gitee.newki123456:round_circle_layout:1.0.0"

详细用法能够看 Maven 项目,源码在这儿,也现已悉数开源。

常规,我如有解说不到位或错漏的当地,期望同学们能够指出交流。

假如感觉本文对你有一点点的启发,还望你能点赞支撑一下,你的支撑是我最大的动力。

Ok,这一期就此结束。

圆角升级啦,来手把手一起实现自定义ViewGroup的各种圆角与背景