背景
来自于需求的视觉设计要求
限制字段长度为320px
超长时按宽度缩小字号
TextView底色高度固定
字体缩小步进1sp
难点
课程卡片为自定义View,封装信息层组件,通过锚定基准比例实时计算获取以下对应信息:
Radius
ImageWidth
ImageHeight
Magin
Padding
TextSize
动态数据加载,等待后端返回参数进行以下设置:
Background
Color
Text
主流实现方案
采用AppCompactTextView的内部属性,必须设置:
动态变化字体大小适应长度标志位:uniform
字体缩小步长:2sp
字体缩小后的最小字体大小:5sp (必须配置 否则放缩不生效)
android:autoSizeMinTextSize="5sp" android:autoSizeStepGranularity="2sp" android:autoSizeTextType="uniform" |
存在问题:
配置与Margin Padding Height Width 等属性存在互相改写的冲突,经过指令重排序后,实现效果不唯一,容易出现多种不同的异常状态;
此配置下,行数为1及TextView高度约束存在冲突无法同时实现,后设定的属性值方生效,即以下属性存在冲突:
android:lines="1" android:maxLine="1" android:isSingleLine="1" android:layout_height="30dp" android:max_height="30dp" |
造成现象:
单行实现时,字体的Padding变位,文本框变大留白;
宽度约束时,出现多行文字,间距很小,文字重叠;
解决方案
来自Android View的通用缩放方法:
正常实现锚定尺寸View;
通过任一数值确定比率;
通过系统方法实现百分比缩小后渲染;
// 等比缩小百分之60 binding.courseCardDetail.scaleX = 0.6F binding.courseCardDetail.scaleY = 0.6F |
存在疑问:
多个布局尤其是自定义布局的缩放涉及的计算量是否过大;
渲染后重新运算再渲染是否影响性能;
等比例放大是否存在分辨率问题;
优化方案
通过介入底层View绘制过程手动实现字体缩小过程:
![](https://img-blog.csdnimg.cn/img_convert/dc35efee8f3927487a797156778bbff3.png)
核心逻辑:
class AdjustableSizeTextView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
/** * 通过文本宽度测量与约束值进行对比 过长文本循环缩短字号至宽度满足父布局约束 */ override fun setText(text: CharSequence?, type: BufferType?) { if (isOnCreateView) { maxTextSize = textSize isOnCreateView = false } var size = maxTextSize setTextSize(TypedValue.COMPLEX_UNIT_PX, size) val rawWith = width - paddingLeft - paddingRight while (rawWith > 0 && rawWith < paint.measureText(text?.toString() ?: "") && size > minTextSize ) { size-- setTextSize(TypedValue.COMPLEX_UNIT_PX, size) } if (size < minTextSize) { setTextSize(TypedValue.COMPLEX_UNIT_PX, minTextSize) } super.setText(text, type) }
} |
<AdjustableSizeTextView android:id="@+id/courseCardDetail" android:layout_width="wrap_content" android:layout_height="37.38dp" android:layout_marginTop="183.19dp" android:layout_marginStart="56.58dp" android:layout_marginEnd="210.14dp" android:background="@color/classcourse_classin_universal" android:gravity="center_vertical|start" android:paddingHorizontal="16.16dp" android:paddingVertical="8.08dp" android:textSize="18.18sp" android:maxLines="1" android:textColor="@color/ss_white" android:textStyle="bold" android:ellipsize="end" app:minSize="6sp" app:maxWidth="323dp" /> |
在流程的OnSizeChanged()实现字符大小的变化,有以下目的:
经过OnMeasure方法,View & Layout成功获取MeasureSpec,可计算出View的宽高;
获取正确的View宽高,方可计算字体所需的宽度,进而可不断缩小字体以满足宽度约束;
字体大小定义完毕后再进行Layout布局,可避免字体调整时对 View 在父容器中的放置位置的影响;
注意事项
动态获取setTextSize的单位是sp,getTextSize的单位是px,直接从dimen文件中拿到的值也是px。可见setTextSize比较特殊,设置时需要根据实际情况进行转换。
若需要约束TextView高度,需在layout_heidge中写入固定值,否则字体过长缩小后,页面onResume时再次触发warp_content导致高度变化.
总结
TextView属性背后容易出现绘制冲突,动态设置参数以最后的配置为准,会影响之前配置好的布局,产生问题;
对于支持API 26以上的 autoSizeTextType 属性,更容易产生布局冲突,导致onDraw结束后所表现的状态出现异常;
采用自定义View并重写绘制流程的方法较为稳定,且不会因为多次重建导致问题。