继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android 项目中 shape 标签的整理和思考(2)

慕神8447489
关注TA
已关注
手记 1310
粉丝 174
获赞 957

我们先来看看 CommonShapeButton 不能解决的应用场景是什么?这里我们需要回顾下这个通用组件,它本身是用来解决 shape 文件泛滥的问题,支持 shape 的各种特性,同时也支持文本样式和按钮样式。但是归根结底 CommonShapeButton 只是一个 View ,它没有办法解决 ViewGroup 的应用场景。而在实际开发过程中,在 ViewGroup 这一层去设置 shape 样式的背景是一个常见的需求。分析到这里,我们得出结论,我们还需要一个通用组件 CommonShapeViewGroup 来协助我们项目开发。

正当笔者准备着手设计这个新的通用组件的时候,脑中突然闪过一个官方提供的组件 CardView ,这个位于 support-v7 下面的谷歌亲儿子,好像已经解决了我们的问题?于是笔者又去啃了一下官方文档,对这个 CardView 做了一个全面的梳理,发现了它的局限性:

  • CardView 继承自 FrameLayout ,而现在主流的 ViewGroup 应该是 ConstraintLayout 和 RelativeLayout。

  • CardView 支持设置背景颜色,但是只能设置纯色,无法设置渐变颜色。

  • CardView 支持设置圆角大小,但是只能同时设置四个角的圆角大小,无法单一设置左侧圆角或者右侧圆角。

  • CardView 只支持矩形一种形状。

  • CardView 不支持设置描边颜色和描边宽度。

没办法,看来谷歌亲儿子也不顶用,还是自己撸吧。

Talk is cheap. Show me the code

第一步,我们需要确定支持的 ViewGroup 有哪些。还是那句话,现在主流的 ViewGroup 应该是 ConstraintLayout 和 RelativeLayout ,这里需要重点推一波 ConstraintLayout ,自从用了它以后,腰也不酸了,腿也不疼了,妈妈再也不用担心我写布局了。但是考虑到我们程序猿都是重感情的人,之前最爱的 RelativeLayout 也不能说有了新欢就不管了是吧,好吧,把 RelativeLayout 加上,就支持这两兄弟了。

第二步,继续思考如何来设计这个通用组件,主要是从以下几个方面进行了考虑:

  • ViewGroup 的设计要比 View 更简单,因为它是纯展示的,没有交互也不需要动效。

  • 直接继承 ConstraintLayout 和 RelativeLayout ,进行背景的动态设置是最为简单有效的方式。

  • 自定义属性方面,完全可以参照 CommonShapeButton ,去掉一些不需要的属性即可。

  • 新增一个阴影属性,提升一下逼格。控件阴影这个问题,在 5.0 以上也就一行代码的事。在 5.0 以下,笔者花了不少时间,用各种方案做出来的效果都不尽人意。本着宁缺毋滥的原则,最终还是选择了放弃。其实主要原因还是 5.0 以下的用户确实越来越少,花费过多的精力去做一些收效甚微的工作也不符合软件工程的思想。当然这方面有兴趣的朋友,可以在文章的后面拿到源码以后进行自己的扩展和修改。

第三步,思路已经梳理清楚了,那就开撸吧。这里就以 ConstraintLayout 为例,

class ShapeConstraintLayout @JvmOverloads constructor(
        context: Context,
        attrs: AttributeSet? = null,
        defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) {

这里选择了直接继承 ConstraintLayout 进行扩展。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec)    // 初始化shape
    with(mGradientDrawable) {        // 渐变色
        if (mStartColor != Color.parseColor("#FFFFFF") && mEndColor != Color.parseColor("#FFFFFF")) {
            colors = intArrayOf(mStartColor, mEndColor)            when (mOrientation) {                0 -> orientation = GradientDrawable.Orientation.TOP_BOTTOM                1 -> orientation = GradientDrawable.Orientation.LEFT_RIGHT
            }
        }        // 填充色
        else {
            setColor(mFillColor)
        }        when (mShapeMode) {            0 -> shape = GradientDrawable.RECTANGLE            1 -> shape = GradientDrawable.OVAL            2 -> shape = GradientDrawable.LINE            3 -> shape = GradientDrawable.RING
        }        // 统一设置圆角半径
        if (mCornerPosition == -1) {
            cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mCornerRadius.toFloat(), resources.displayMetrics)
        }        // 根据圆角位置设置圆角半径
        else {
            cornerRadii = getCornerRadiusByPosition()
        }        // 默认的透明边框不绘制
        if (mStrokeColor != Color.parseColor("#00000000")) {
            setStroke(mStrokeWidth, mStrokeColor)
        }
    }    // 设置背景
    background = mGradientDrawable    // 5.0以上设置阴影
    if (mWithElevation && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        elevation = DEFAULT_ELEVATION
    }
}

核心代码依然选择在 onMeasure 方法中实现,我们做一个简单的分析:

  • 首先对 mGradientDrawable 设置当前是渐变色渲染还是填充色渲染,渐变色渲染还需要单独控制渲染的方向。

  • 然后对 mGradientDrawable 设置 shape 模式、圆角以及描边。这里的圆角设置区分了统一设置四个角还是根据圆角位置设置。

  • 然后设置 ViewGroup 的背景。

  • 最后在 5.0 以上设置控件阴影。

到这里,就完成了核心实现。下面我们看一下根据圆角位置设置圆角半径的具体实现:

/**
 * 根据圆角位置获取圆角半径
 */private fun getCornerRadiusByPosition(): FloatArray {    val result = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f)    val cornerRadius = mCornerRadius.toFloat()    if (containsFlag(mCornerPosition, TOP_LEFT)) {
        result[0] = cornerRadius
        result[1] = cornerRadius
    }    if (containsFlag(mCornerPosition, TOP_RIGHT)) {
        result[2] = cornerRadius
        result[3] = cornerRadius
    }    if (containsFlag(mCornerPosition, BOTTOM_RIGHT)) {
        result[4] = cornerRadius
        result[5] = cornerRadius
    }    if (containsFlag(mCornerPosition, BOTTOM_LEFT)) {
        result[6] = cornerRadius
        result[7] = cornerRadius
    }    return result
}/**
 * 是否包含对应flag
 */private fun containsFlag(flagSet: Int, flag: Int): Boolean {    return flagSet or flag == flagSet
}

简单分析一下:

  • 自定义圆角位置支持四个方位的:TOP_LEFT、TOP_RIGHT、BOTTOM_RIGHT、BOTTOM_LEFT。

  • 通过自定义属性中的 flag 标签设置了圆角方位支持按位或运算。

  • 生成四个角对应的8位数组,解析 xml 属性根据按位或运算设置对应方位的圆角半径。

到这里,也就是 CommonShapeViewGroup 的全部实现了。其实笔者写到这里的时候,陷入了一个思考,我们到现在实现了 CommonShapeButton 和 CommonShapeViewGroup ,其实这两者的本质都是用代码去实现 shape 效果,也就是对 GradientDrawable 的二次封装,那么我们是不是实现一个封装以后的 CommonShapeDrawable 就可以解决所有问题呢?TextView 、Button 、ConstraintLayout 、RelativeLayout等等以及其他的应用场景都可以适配。笔者产生了这个想法以后,就马上去实现了一个。但是实际开发用起来以后,发现它并不像我们想象的那么方便,需要创建一个 CommonShapeDrawable 对象,然后逐一调用对应的方法去设置 shape 效果,最后还要在一个恰当的时机设置成控件的背景。这跟我们通过 xml 自定义属性就能实现效果来比,繁琐了不少,最终还是选择了放弃。有兴趣的朋友也可以通过这两篇博客的学习,自己去撸一个出来。

题外话说了这么多,这里还是回到 CommonShapeViewGroup ,照例贴上全部的自定义属性:

<declare-styleable name="CommonShapeViewGroup">
    <attr name="csvg_shapeMode" format="enum">
        <enum name="rectangle" value="0" />
        <enum name="oval" value="1" />
        <enum name="line" value="2" />
        <enum name="ring" value="3" />
    </attr>
    <attr name="csvg_fillColor" format="color" />
    <attr name="csvg_strokeColor" format="color" />
    <attr name="csvg_strokeWidth" format="dimension" />
    <attr name="csvg_cornerRadius" format="dimension" />
    <attr name="csvg_cornerPosition">
        <flag name="topLeft" value="1" />
        <flag name="topRight" value="2" />
        <flag name="bottomRight" value="4" />
        <flag name="bottomLeft" value="8" />
    </attr>
    <attr name="csvg_startColor" format="color" />
    <attr name="csvg_endColor" format="color" />
    <attr name="csvg_orientation" format="enum">
        <enum name="TOP_BOTTOM" value="0" />
        <enum name="LEFT_RIGHT" value="1" />
    </attr>
    <attr name="csvg_withElevation" format="boolean" /></declare-styleable>

以下是效果图:

webp



作者:xuyefeng
链接:https://www.jianshu.com/p/fdcf38516a61


打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP