作者:opLW
最近做毕设时自己设计的书签,无奈最终效果和想象中的不太一样(有点丑感觉),可能我需要一个美工妹子😭。
目录
1.书签 – 简介
2.阴影、背景图案的处理
3.对原始padding的支持
4.总结
1.书签 – 简介
- 最终功能展示
PS: 通过点击控件可在正常状态和下沉状态之间进行切换。
下沉状态下图片的阴影各项指标将为正常状态的一半,同时字体变得模糊。 - 方案 想到如下两个方案,经过考虑使用继承
TextView
来实现。- 继承View
- 做法 直接继承自
View
,然后重写View
的onDraw
方法,绘制阴影、背景图案、字体等。 - 不足 除了绘制阴影、不规则图案外,还需要考虑字体绘制带来的难点,包括字体大小、
gravity
等,难度相当之大。
- 做法 直接继承自
- 继承TextView
- 做法 继承自
TextView
,重写onDraw
方法,绘制阴影、背景图案 - 优点 对于字体的处理等就不需要考虑,交给
TextView
处理即可,大大降低了难度。只需考虑如何绘制字体外的阴影和背景图案。
- 做法 继承自
- 继承View
2.阴影、背景图案的处理
- 总体思路
- 问题 为了保留
TextView
的大多数功能,使用继承自View
的方案。需要考虑的是如何在不影响TextView
核心内容的情况下绘制阴影、背景图案。 - 思考 其实阴影、背景图案都是在包含字体的框框外部进行处理,很容易可以联想到
padding
。因为padding
就是内容与控件四周的距离,padding
越大,预留给我们进行处理的空间也就越大。所以只需要计算出需要显示的阴影、背景图案的大小,预留足够的padding进行绘制即可。
- 问题 为了保留
- padding的计算
- 介绍
1.黑色边框为整个控件的边界;
2.黑色线条和蓝色线条之间的空间为阴影需要的大小,(由于左右两侧还需要考虑切角的大小,所以作此标志);
3.蓝色线条和红色线条之间的空间为需要裁剪的大小;
4.最内的红色边框为不进行任何设置时的边界,即普通TextView
不设置padding
的边界。 - 计算留给阴影的padding
- 示例代码
shadowSizeL = Math.max(0f, -bShadowDx) + Math.max(0f, bShadowRadius) shadowSizeT = Math.max(0f, -bShadowDy) + Math.max(0f, bShadowRadius) shadowSizeR = Math.max(0f, bShadowDx) + Math.max(0f, bShadowRadius) shadowSizeB = Math.max(0f, bShadowDy) + Math.max(0f, bShadowRadius)
- 注意点
- 对值为负的处理 使用
Math.max
函数限制参数最小值为0。 - 对shadowDx和shadowDy为负时的处理 当这两个值为负时,代表阴影向左、向上偏移。而留给阴影的空间为正值,所以对这两个方向的计算,需要添加“-”号,如
shadowSizeL、shadowSizeT
的计算。 - 预留空间给bShadowRadius 实践证明,即使阴影没有发生偏移,阴影本身的
radius
也会使得控件周围有一圈阴影,所以为了显示这一圈的阴影,需要考虑bShadowRadius
的大小。
- 对值为负的处理 使用
- 示例代码
- 计算留给切角的padding
- 示例代码 没什么特别之处,就是简单的限制了切角的大小。
clipSizeL = Math.min(measuredWidth / 4, Math.max(leftClipX, 0)).toFloat() clipSizeR = Math.min(measuredWidth / 4, Math.max(rightClipX, 0)).toFloat()
- 示例代码 没什么特别之处,就是简单的限制了切角的大小。
- 介绍
- padding的设置时机 考虑到需要对左右切角的大小进行限制,避免切角太大。而限制因素是控件本身的宽度,所以我们将
padding
的设置置于onMeasure
方法中,因为在此方法中可以获得控件初步测量得到的大小:measureWidth
。override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { setPaddingToDrawBG() super.onMeasure(widthMeasureSpec, heightMeasureSpec) } private fun setPaddingToDrawBG() { val left = calculatePaddingLeft().toInt() val top = calculatePaddingTop().toInt() val right = calculatePaddingRight().toInt() val bottom = calculatePaddingBottom().toInt() setPadding(left, top, right, bottom) } private fun calculatePaddingLeft(): Float { clipSizeL = Math.min(measuredWidth / 4, Math.max(leftClipX, 0)).toFloat() shadowSizeL = Math.max(0f, -bShadowDx) + Math.max(0f, bShadowRadius) return clipSizeL + shadowSizeL + paddingL } ......
- 设置影响最终绘制效果的参数
- 参数如下:
/** * 划分阴影和裁剪区域,即前面提到的蓝色边框。 */ private val clipBorderL: Float get() { return shadowSizeL } private val clipBorderR: Float get() { return width - shadowSizeR } /** * 内容物的左边界,边界右侧为不受裁剪影响的区域。 * 同理的还有contentBorderT、contentBorderR、contentBorderB。 * 下述四者组成了前面提到的红色边框。 */ private val contentBorderL: Float get() { return shadowSizeL + clipSizeL } private val contentBorderT: Float get() { return shadowSizeT } private val contentBorderR: Float get() { return width - shadowSizeR - clipSizeR } private val contentBorderB: Float get() { return height - shadowSizeB }
Kotlin
语言的val
属性,默认自带get
方法,上述语法为重写自带的get
方法;
- 为何不在计算padding时设置这些参数,而采取重写get方法的方式?
- 最后
onDraw
要使用到的各项参数,需要根据控件最终的width
和height
进行计算,而最终的宽高需要算上留给阴影、背景图案的padding
,因此不能在计算padding
时设置; Kotlin
中访问属性时,实质上调用的是该属性的get
方法,所以最终访问该属性时,会调用到重写后的get
方法,此时属性的值会再次进行计算,从而保证onDraw
方法中用到的属性值受控件最终大小的限制。
- 最后
- 参数如下:
3.对原始padding的支持
- 方法 通过自定义参数
paddingL
等实现对原始padding
的支持,如计算padding
时用到的函数:private fun calculatePaddingLeft(): Float { clipSizeL = Math.min(measuredWidth / 4, Math.max(leftClipX, 0)).toFloat() shadowSizeL = Math.max(0f, -bShadowDx) + Math.max(0f, bShadowRadius) // 这里使用自定义参数paddingL而不是paddingLeft return clipSizeL + shadowSizeL + paddingL }
- 使用自定义参数的原因
-
受父布局的影响,一般
View
的onMeasure
方法会被多次调用(具体原因尚不是很清晰😝),所以在onMeasure
中的setPaddingToDrawBG()
方法会被多次调用,即会多次设置padding
。 -
假设这里不使用自定义的参数
paddingL
等,而是使用View
自带的属性paddingLeft
,那么上一次设置的padding
值会影响到当前padding
的设置,最终导致padding
的值为一个累加的效果。
-
- paddingL的赋值时机 在控件初始化时进行记录,即
Kotlin
中的init
方法被调用时。init { ...... // 其他参数的设置 paddingL = paddingLeft paddingT = paddingTop paddingR = paddingRight paddingB = paddingBottom } ```
- 对原始padding支持的前后对比
4.总结
- 对
padding
的灵活使用,使得控件在满足大部分TextView
功能的前提下,又含有阴影、背景图案、原始padding
等功能。 - 暂时不支持
android:singleLine="true"
属性,设置之后导致阴影、背景图案丢失(后面再研究如何支持😁)。 - 对于阴影、背景图案的绘制,这里不做详细介绍,详情可见BookMark,欢迎赐教❤。
万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。
opLW原创七言律诗,转载请注明出处