文字缩放 Autosizing
文字缩放 Autosizing
TextView 文字缩放
Autosizing(TextView 文本大小自动适配)
Autosizing 方式
官方推出的 TextView 的 Autosizing 方式,在宽度固定的情况下,可以设置最大文本 Size 和最小文本 Size 和每次缩放粒度。
Autosizing 使用
xml
XML 方式:
1
2
3
4
app:autoSizeTextType="uniform"
app:autoSizeMaxTextSize="13sp"
app:autoSizeMinTextSize="5sp"
app:autoSizeStepGranularity="1sp"
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
<TextView
android:layout_width="340dp"
android:layout_height="50dp"
android:background="@drawable/shape_bg_008577"
android:gravity="center_vertical"
android:maxLines="1"
android:text="这是标题,该标题的名字比较长,产品要求不换行全部显示出来"
android:textSize="18sp"
android:autoSizeTextType="uniform"
android:autoSizeMaxTextSize="18sp"
android:autoSizeMinTextSize="10sp"
android:autoSizeStepGranularity="1sp"/>
属性解释:
- autoSizeTextType:设置 TextView 是否支持自动改变文本大小,none 表示不支持,uniform 表示支持。
- autoSizeMinTextSize:最小文字大小,例如设置为 10 sp,表示文字最多只能缩小到 10 sp。
- autoSizeMaxTextSize:最大文字大小,例如设置为 18 sp,表示文字最多只能放大到 18 sp。
- autoSizeStepGranularity:缩放粒度,即每次文字大小变化的数值,例如设置为 1 sp,表示每次缩小或放大的值为 1 sp。
代码
代码中使用:
1
2
3
TextView tvText = findViewById(R.id.tv_text);
TextViewCompat.setAutoSizeTextTypeWithDefaults(tvText,TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM);
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(tvText,10,18,1, TypedValue.COMPLEX_UNIT_SP);
Autosizing 不生效
原因
Autosizing 在不同的宽高 wrap_content,是否多行,都生效,前提是已经摆放不下了
原因:设置了 android:singleLine="true"
解决 1:存在 ellipsize=middle
去掉 singleLine=true
,改成 maxLines=1 即可,但如果用到了 ellipsize=middle
,就不行了,需要用代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private fun autoSizeWithSingleLine(
textView: TextView,
text: CharSequence,
fontSizes: List<Float> = listOf(13F, 12F, 11F, 10F),
) {
val availableWidth = textView.width - textView.paddingLeft - textView.paddingRight
if (availableWidth <= 0) return
val paint = textView.paint
for (textSizeSp in fontSizes.sortedDescending()) {
val textSizePx = SUIUtils.sp2px(textView.context, textSizeSp)
paint.textSize = textSizePx.toFloat()
if (paint.measureText(text.toString()) <= availableWidth) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSizeSp)
break
}
}
textView.text = text
}
解决 2
- 不要在
layout_width
和layout_height
使用wrap_content
- 不要用
android:singleLine
,而是用android:maxLines=1
;注意ellipsize=middle
只能使用 singleLine - 用
app:autosizexxx
替代android:autosizexxx
以支持旧版本 - Autosize 支持
AppCompatTextView
andTextView
Ref: Autosizing of TextView doesn’t work (Android O) - Stack Overflow
自定义 View 的方式(固定宽度)
其核心思想和上面的 Autosizing 的方式类似,一般是测量 TextView 字体所占的宽度与 TextView 控件的宽度对比,动态改变 TextView 的字体大小。
类似:AutoSizeTextView
使用工具类自行计算(非控件固定宽度)
自定义 View 计算宽度的方法抽取出来:
- 基于当前 textSize 来获取宽度
- 如果获取的宽度小于等于可用的宽度,就设置该文字大小,否则一直循环重复 1~2 步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
private void adjustTvTextSize(TextView tv, int maxWidth, String text) {
int avaiWidth = maxWidth - tv.getPaddingLeft() - tv.getPaddingRight();
if (avaiWidth <= 0) {
return;
}
TextPaint textPaintClone = new TextPaint(tv.getPaint());
float trySize = textPaintClone.getTextSize();
while (textPaintClone.measureText(text) > avaiWidth) {
trySize--;
textPaintClone.setTextSize(trySize);
}
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);
}
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/d_15dp"
android:layout_marginRight="@dimen/d_15dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_job_detail_dollar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="$"
android:textColor="@color/black"
android:textSize="@dimen/job_detail_message_size"/>
<TextView
android:id="@+id/text_view_hourly_rate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/d_2dp"
android:singleLine="true"
android:text="-"
android:textColor="@color/job_detail_black"
android:textSize="30sp" />
</LinearLayout>
可以看到 2 个都是 wrap content,那么如何实现这种适应宽度 + 多布局的变长宽度效果呢。其实就是需要我们调用方法手动的计算金额 TextView 的宽度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int mFullNameTVMaxWidth = CommUtils.dip2px(60);
// mTextViewHourlyRate.setText(totalMoney);
// while (true) {
// float measureTextWidth = mTextViewHourlyRate.getPaint().measureText(totalMoney);
// if (measureTextWidth > mFullNameTVMaxWidth) {
// int textSize = (int) mTextViewHourlyRate.getTextSize();
// textSize = textSize - 2;
// mTextViewHourlyRate.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
// } else {
// break;
// }
// }
adjustTvTextSize(mTextViewHourlyRate,mFullNameTVMaxWidth,totalMoney)
效果:
1 行展示不下,先缩放字体到最小大小 10 sp,还展示不下,设置展示 2 行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
val textPaintClone = TextPaint()
private fun adjustTvTextSize(
tv: TextView,
maxLineWidth: Int,
text: String,
defaultTextSizeSp: Float = 18F,
minTextSizeSp: Float = 10F
) {
val textSizeQueue: ArrayDeque<Float> by lazy {
ArrayDeque<Float>().apply {
add(18f)
add(16f)
add(14f)
add(12f)
add(10f)
}
}
val availWidth = maxLineWidth - tv.paddingStart - tv.paddingEnd - 6F.dp()
if (availWidth <= 0) {
return
}
tv.maxLines = 1
tv.textSize = defaultTextSizeSp
textPaintClone.set(tv.paint)
textPaintClone.textSize = ScreenUtils.sp2px(this, defaultTextSizeSp).toFloat()
var tryTextSizeSp = defaultTextSizeSp
Log.v(
"hacket",
"start tryTextSizeSp(sp)=${tryTextSizeSp}, defaultTextSizeSp=$defaultTextSizeSp, minTextSizeSp=$minTextSizeSp, textSizeQueue=$textSizeQueue"
)
var i = 1
while (textSizeQueue.isNotEmpty() && textPaintClone.measureText(text) >= availWidth) {
val tempTextSize = textSizeQueue.removeFirstOrNull() ?: 0F
if (tempTextSize <= 0F) {
continue
}
if (tryTextSizeSp <= minTextSizeSp) {
continue
}
textPaintClone.textSize = ScreenUtils.sp2px(this, tempTextSize).toFloat()
tv.textSize = tempTextSize
// val isAvail = textPaintClone.measureText(text) > availWidth
// if (!isAvail) {
// continue
// }
tryTextSizeSp = tempTextSize
Log.v(
"hacket", "第${i}次 尝试,measureText=${
textPaintClone.measureText(
text
)
}, availWidth=$availWidth, tryTextSizeSp=$tryTextSizeSp(${
ScreenUtils.sp2px(
this, tryTextSizeSp
).toFloat()
}), minTextSizeSp=$minTextSizeSp, textSizeQueue=$textSizeQueue"
)
i++
}
val avail = textPaintClone.measureText(text) > availWidth
if (avail) {
tv.maxLines = 2
}
Log.w("hacket", "final tryTextSizeSp=${tryTextSizeSp}, avail=$avail")
}
xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tvCouponVal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingHorizontal="27dp"
android:text="MIỄN PHÍ VẬN CHUYỂN"
android:textColor="@color/sui_color_discount"
android:textFontWeight="700"
android:textSize="@dimen/sui_text_size_18"
android:textStyle="bold"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/ivCouponIcon"
app:layout_constraintStart_toStartOf="@id/ivCouponIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.15" />
工具类
根据文本长度动态调整字体大小
- 先设置为 1 行
- 根据提供的字体大小 List,一个个去试
- 如果有 ellipsis,说明一行摆放不下,继续尝试下一个字体大小
- 如果没有 ellipsis,说明一行摆的下
- 最终都会设置 maxLine 为传入的
- 根据行数设置 padding
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 根据文本长度动态调整字体大小
*/
fun TextView.setTextWithSuitableSize(
msg: String?,
textWidth: Int,
atLeastSize: Float = 12f,
paddingTopBottom: Int = DensityUtil.dp2px(2f),
textSizeList: ArrayList<Float> = arrayListOf(12f, 11f, 10f, 9f, 8f),
maxLine: Int = 2
) {
maxLines = 1
val sizeQueue = LinkedBlockingQueue(textSizeList)
text = msg
if (msg.isNullOrEmpty()) {
textSize = atLeastSize
return
}
var ellipseCount = 1
while (ellipseCount > 0 && sizeQueue.isNotEmpty()) {
val fixSize = sizeQueue.poll() ?: atLeastSize
if (fixSize > atLeastSize) continue
textSize = fixSize
measure(
View.MeasureSpec.makeMeasureSpec(textWidth, View.MeasureSpec.EXACTLY),
View.MeasureSpec.UNSPECIFIED
)
ellipseCount = layout?.getEllipsisCount(lineCount - 1) ?: 0
}
maxLines = maxLine
if (lineCount == 1) {
setPadding(paddingStart, 0, paddingEnd, 0)
} else if (lineCount == 2) {
setPadding(paddingStart, paddingTopBottom, paddingEnd, paddingTopBottom)
}
}
应用
TextView 1 行展示不下,先缩放字体,还展示不下,设置展示 2 行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
val textPaintClone = TextPaint()
private fun adjustTvTextSize(
tv: TextView,
maxWidth: Int,
text: String,
defaultTextSizeSp: Float = 18F,
minTextSizeSp: Float = 10F
) {
val textSizeQueue: ArrayDeque<Float> by lazy {
ArrayDeque<Float>().apply {
add(18f)
add(16f)
add(14f)
add(12f)
add(10f)
}
} val availWidth = maxWidth - tv.paddingStart - tv.paddingEnd - 6F.dp()
if (availWidth <= 0) {
return
}
tv.maxLines = 1
tv.textSize = defaultTextSizeSp
textPaintClone.set(tv.paint)
textPaintClone.textSize = ScreenUtils.sp2px(this, defaultTextSizeSp).toFloat()
var tryTextSizeSp = defaultTextSizeSp
Log.v(
"hacket",
"start tryTextSizeSp(sp)=${tryTextSizeSp}, defaultTextSizeSp=$defaultTextSizeSp, minTextSizeSp=$minTextSizeSp, textSizeQueue=$textSizeQueue"
)
var i = 1
while (textSizeQueue.isNotEmpty() && textPaintClone.measureText(text) >= availWidth) {
val tempTextSize = textSizeQueue.removeFirstOrNull() ?: 0F
if (tempTextSize <= 0F) {
continue
}
if (tryTextSizeSp <= minTextSizeSp) {
continue
}
textPaintClone.textSize = ScreenUtils.sp2px(this, tempTextSize).toFloat()
// val isAvail = textPaintClone.measureText(text) > availWidth
// if (!isAvail) {
// continue
// }
tryTextSizeSp = tempTextSize
Log.v(
"hacket", "第${i}次 尝试,measureText=${
textPaintClone.measureText(
text
)
}, availWidth=$availWidth, tryTextSizeSp=$tryTextSizeSp(${
ScreenUtils.sp2px(
this, tryTextSizeSp
).toFloat()
}), minTextSizeSp=$minTextSizeSp, textSizeQueue=$textSizeQueue"
)
i++
}
val avail = textPaintClone.measureText(text) > availWidth
if (avail) {
tv.maxLines = 2
}
tv.textSize = tryTextSizeSp
Log.w("hacket", "final tryTextSizeSp=${tryTextSizeSp}, avail=$avail")
}
本文由作者按照 CC BY 4.0 进行授权