d81d3b6d22b2

topLogo.gif

The phantom menace

我在开发的时候遇到这样一个恶心的场景:一个TextView(本业务中是指horde_hall_name)的宽、高、字体大小(maxFontSize=20dp)都是固定的,但是文案不固定,文案短的话没问题,文案太长的话会显示不全。这给我造成了危机感(menace),我想到的解决方案是根据TextView的宽度来找到不大于maxFontSize的最大字号以保证文案能够完全显示,代码演示:

private void setHordeNameTextSize() {

if (horde_hall_name_width <= 0 || mHordeEntity == null) {

return;

}

int paddingLeftOrRight = ScreenUtil.dip2px(10);

float maxTextSize = ScreenUtil.dp2px(this, 20);//最大20dp

TextPaint tp = horde_hall_name.getPaint();

if (tp.getTextSize() >= maxTextSize) {

maxTextSize = getDesiredTextSize(horde_hall_name, mHordeEntity.name, horde_hall_name_width - 2 * paddingLeftOrRight, ScreenUtil.dp2px(this, 20), true);//最大20dp

} else {

maxTextSize = getDesiredTextSize(horde_hall_name, mHordeEntity.name, horde_hall_name_width - 2 * paddingLeftOrRight, ScreenUtil.dp2px(this, 20), false);//最大20dp

}

horde_hall_name.setTextSize(TypedValue.COMPLEX_UNIT_DIP, ScreenUtil.px2dip(maxTextSize));

horde_hall_name.setText(mHordeEntity == null ? "" : mHordeEntity.name);

LogUtil.i(TAG, "maxTextSize: " + maxTextSize);

}

private float getDesiredTextSize(TextView textView, String text, int maxWidth, int maxTextSize, boolean toSmall) {

TextPaint tp = textView.getPaint();

if (toSmall) {

while (tp.measureText(text) > maxWidth) {

tp.setTextSize(tp.getTextSize() - ScreenUtil.density);

getDesiredTextSize(textView, text, maxWidth, maxTextSize, toSmall);

}

return tp.getTextSize() > maxTextSize ? maxTextSize : tp.getTextSize();

} else {

while (tp.measureText(text) < maxWidth - 10) {

tp.setTextSize(tp.getTextSize() + ScreenUtil.density);

getDesiredTextSize(textView, text, maxWidth, maxTextSize, toSmall);

}

return tp.getTextSize() > maxTextSize ? maxTextSize : tp.getTextSize();

}

}

如果你是一个合格的androider,相信应该能看懂的。这样循环查找最优的字号性能很差,如果一个RecyclerView的每个item都用这种方式的话,估计页面会很卡,只适合单个TextView这样操作。

A New Hope

Android 8.0带来了a new hope,可以根据 TextView 的大小自动设置文本展开或收缩的大小。这意味着,在不同屏幕上优化文本大小或者优化包含动态内容的文本大小比以往简单多了。

API最终的目的是TextView能保证文本在字体大小阈值内占满控件的空间,如果文本达到了设置的字体范围的最大、最小值时,文本大小不会再继续变化。

API使用方式很简单,简单到只有一行代码呦:

android:layout_width="180dp"

android:layout_height="180dp"

app:autoSizeTextType="uniform"/>

android:autoSizeTextType有两个值:取值none(默认,表示不自动缩放)、uniform(横、纵缩放)。

通过代码代码控制的话是这样写:

TextViewCompat . setAutoSizeTextTypeWithDefaults ( TextView textview , int autoSizeTextType )

至于该怎么缩放呢,autosizing textview有两种模式

粒度型(Granularity)

预置大小型(PresetSizes)

Granularity设置字体大小的最小值和最大值的变化范围,然后设置一个变化粒度值,TextView大小就不断增减变量该粒度值,在变化范围内均匀地动态缩放变化(你可以试下不设置最小值和最大值,我试了下,貌似有个默认的最小值,最大值貌似是无穷大)。代码演示:

android:id="@+id/tv_autosize"

android:layout_width="180dp"

android:layout_height="180dp"

app:autoSizeMinTextSize="20dp"

app:autoSizeMaxTextSize="80dp"

android:text="Hola, amigo"

app:autoSizeStepGranularity="2dp"

app:autoSizeTextType="uniform"/>

PresetSizes预置一个字体大小的数组,TextView从数组中选择合适的字体大小自动调整。

代码演示:

android:id="@+id/tv_autosize"

android:layout_width="180dp"

android:layout_height="180dp"

android:text="Hola, amigo"

app:autoSizeStepGranularity="2dp"

app:autoSizePresetSizes="@array/preset_sizes"

app:autoSizeTextType="uniform"/>

12dp

24dp

36dp

48dp

60dp

72dp

84dp

需要注意的是PresetSizes优先级比Granularity优先级高,如果两者同时设置,那么只有

PresetSizes会生效,为什么呢,我猜应该PresetSizes效率更高,毕竟是已经预置了字体大小呢!!

amigo,是不是很简单,很easy???

RETURN OF THE JEDI

有了android oreo提供的新的API,那么绝地归来,文章开头那个困扰我的问题也有了更优雅的解决方案了。

先举个简单的案例吧,此案例很简单,一个Checkbox控制采用Granularity模式Preset size模式;两个Button分别增大和减小TextView布局宽高,然后观察TextView的尺寸变化。代码演示:

xml代码:

xmlns:app="http://schemas.android.com/apk/res-auto"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical">

android:id="@+id/ck_preset"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:checked="false"

android:text="预置"/>

android:layout_width="wrap_content"

android:layout_height="wrap_content">

android:id="@+id/btn_plus"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="加大"/>

android:id="@+id/btn_minus"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="缩小"/>

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical">

android:id="@+id/autosize_textView"

android:layout_width="60dp"

android:layout_height="30dp"

android:layout_gravity="center"

app:android:autoSizeMinTextSize="20dp

app:android:autoSizeMaxTextSize="80dp"

android:background="@android:color/holo_red_dark"

android:text="Hola, amigo"

app:autoSizeStepGranularity="2dp"

app:autoSizeTextType="uniform"/>

android:id="@+id/autosize_textView_preset_sizes"

android:layout_width="60dp"

android:layout_height="30dp"

android:layout_gravity="center"

android:layout_marginTop="5dp"

android:background="@android:color/holo_blue_bright"

android:text="Hola, amigo"

app:autoSizePresetSizes="@array/preset_sizes"

app:autoSizeTextType="uniform"/>

kotlin代码:

fun lazyFast(operation: () -> T): Lazy = lazy(LazyThreadSafetyMode.NONE) {

operation()

}

var mChangeStep = lazyFast {

resources.displayMetrics.density * 2

}

lateinit var operateTextView: TextView

private fun testAutoSizeTextView() {

//ids: ck_preset btn_plus btn_minus autosize_textView autosize_textView_preset_sizes

autosize_textView.visibility = if (ck_preset.isChecked) View.INVISIBLE else View.VISIBLE

autosize_textView_preset_sizes.visibility = if (ck_preset.isChecked) View.VISIBLE else View.INVISIBLE

operateTextView = autosize_textView

ck_preset.setOnCheckedChangeListener { buttonView, isChecked ->

autosize_textView.visibility = if (ck_preset.isChecked) View.INVISIBLE else View.VISIBLE

autosize_textView_preset_sizes.visibility = if (ck_preset.isChecked) View.VISIBLE else View.INVISIBLE

operateTextView = if (ck_preset.isChecked) autosize_textView_preset_sizes else autosize_textView

}

btn_plus.setOnClickListener {

val ll = operateTextView.getLayoutParams()

ll.width += mChangeStep.value.toInt()

ll.height += mChangeStep.value.toInt()

operateTextView.setLayoutParams(ll)

}

btn_minus.setOnClickListener {

val ll = operateTextView.getLayoutParams()

ll.width -= mChangeStep.value.toInt()

ll.height -= mChangeStep.value.toInt()

operateTextView.setLayoutParams(ll)

}

}

granularity的效果图:

d81d3b6d22b2

[图片上传中...(presetSize.gif-feff7a-1513772665910-0)]

preset size效果图:

d81d3b6d22b2

presetSize.gif

参考文献:

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐