_Android 项目中 shape 标签的整理和思考

关于 shape 标签如何使用,在网上一搜一大把,笔者就不在这里赘述了,今天我们要讨论的是 shape 标签泛滥成灾以后带来的后果。这里先给大家看一个维护超过了 5 年的项目的 drawable 目录

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

请注意右侧标红的滚动条,有没有感觉很酸爽,在这个目录下的文件现在已经超过了 500 个,并且还在不停的增加。我们分析这个目录下的 xml 构成,发现主要由两种类型构成:selector 和 shape。selector 这里略过不提,重点关注 shape,发现 shape 文件已经超过了 200 个并且还在不停的增加。我们再带着好奇的心态随便点开几个 shape 看一看

<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>

真的是不看不知道,一看吓一跳。原来我们项目中大量存在的 shape 文件其实都是大同小异的,涉及到最常见的 shape 变化:圆角,描边,填充以及渐变。 进一步分析,我们又发现:

  • 有些时候填充颜色是相同的,只不过圆角半径不同,我们就得新增一个 shape 文件。
  • 有些时候圆角半径是相同的,只不过填充颜色不同,我们又得新增一个 shape 文件。
  • 有些时候两个负责不同业务模块的同事,各自新增一个同样样式的 shape 文件。

等等一些情况,让我们陷入了 shape 文件的无限新增与维护中。我们不禁要思考,有没有办法可以把这些 shape 统一起来管理呢?xml 书写出来的代码最终不都是会对应一个内存中的对象吗?我们能不能从管理 shape 文件过度到管理一个对象呢?

Talk is cheap. Show me the code

第一步,我们需要确定 shape 标签对应的类到底是哪一个?第一反应就是 ShapeDrawable,顾名思义嘛。然后残酷的事实告诉我们其实是 GradientDrawable 这兄弟。浏览 GradientDrawable 类的方法结构,从中我们也找到了setColor()、setCornerRadius()、setStroke() 等目标方法。好吧,不管怎样,先找到正主了。

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

  • shape 的应用场景有可能是文字标签,也有可能是响应按钮,所以需要文本和按钮两种样式,两者的主要区别在于按钮样式在普通状态下和按压状态下都具有阴影。
  • 为了提升用户体验,设计了通用控件的按压动效。针对 5.0 以上的用户开启按压水波纹效果,针对 5.0 以下的用户开启按压变色效果。 结合以上两点,通用控件的实现考虑直接继承 AppCompatButton 进行扩展。
  • 具体的业务场景中,通用控件的使用还有可能伴随着 drawable,并且要求 drawable 和文字一起居中显示。其实这个问题本来是不需要单独考虑的,但是 Android 有个坑,在一个按钮控件中设置 drawable 以后,默认是贴着控件边缘显示的,所以这个坑需要单独填。
  • 自定义控件属性支持 shape 模式、填充颜色、按压颜色、描边颜色、描边宽度、圆角半径、按压动效是否开启、渐变开始颜色、渐变结束颜色、渐变方向、drawable 方位。

第三步,思路已经梳理清楚了,那就开撸。

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

这里实现了继承 AppCompatButton 进行扩展,默认样式 defStyleAttr 传递的是 0,那么 CommonShapeButton 的默认表现形式就是文本样式。

如果想要采用按钮样式,则需要先自定义一个按钮样式,原因是系统按钮的样式自带了 minWidth、minHeight 以及 padding,在具体业务中会影响到我们的按钮显示,所以在自定义按钮样式中重置了这三个属性:

有了自定义按钮样式,那么想要 CommonShapeButton 采用按钮样式,则采用如下形式:

<com.blue.view.CommonShapeButton
style=“@style/CommonShapeButtonStyle”
android:layout_width=“300dp”
android:layout_height=“50dp”/>

到这里就可以实现简单的文本样式和按钮样式的切换了。 接下来我们就要进行关键的 shape 渲染了:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// 初始化normal状态
with(normalGradientDrawable) {
// 渐变色
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
}
cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mCornerRadius.toFloat(), resources.displayMetrics)
// 默认的透明边框不绘制,否则会导致没有阴影
if (mStrokeColor != Color.parseColor(“#00000000”)) {
setStroke(mStrokeWidth, mStrokeColor)
}
}

// 是否开启点击动效
background = if (mActiveEnable) {
// 5.0以上水波纹效果
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
RippleDrawable(ColorStateList.valueOf(mPressedColor), normalGradientDrawable, null)
}
// 5.0以下变色效果
else {
// 初始化pressed状态
with(pressedGradientDrawable) {
setColor(mPressedColor)
when (mShapeMode) {
0 -> shape = GradientDrawable.RECTANGLE
1 -> shape = GradientDrawable.OVAL
2 -> shape = GradientDrawable.LINE
3 -> shape = GradientDrawable.RING
}
cornerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mCornerRadius.toFloat(), resources.displayMetrics)
setStroke(mStrokeWidth, mStrokeColor)
}

// 注意此处的add顺序,normal必须在最后一个,否则其他状态无效
// 设置pressed状态
stateListDrawable.apply {
addState(intArrayOf(android.R.attr.state_pressed), pressedGradientDrawable)
// 设置normal状态
addState(intArrayOf(), normalGradientDrawable)
}
}
} else {
normalGradientDrawable
}
}

这里的代码有点长,别着急,我们来慢慢分析一下:

  • 首先是选择在 onMeasure 方法中做shape渲染
  • 其次对 normarlGradientDrawable 设置当前是渐变色渲染还是填充色渲染,渐变色渲染还需要单独控制渲染的方向
  • 然后对 normarlGradientDrawable 设置 shape 模式、圆角以及描边
  • 最后对CommonShapeButton设置background。如果没有开启点击特效,则直接返回normarlGradientDrawable。如果开启了点击特效,那么 5.0 以上启用水波纹效果,5.0 以下启用变色效果。在变色效果的设置中同样初始化了 pressedGradientDrawable 的 shape 属性,并且依次添加进了 stateListDrawable 用作背景显示

到这里就可以实现了用自定义属性控制shape渲染显示 CommonShapeButton 的背景了,这里贴上全部的属性:

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门**

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 28
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值