前言
之前我在FlycoTabLayout的基础上写了一个SlidingScaleTabLayout,实现ViewPager切换Tab标题文字大小变化的效果:
可能是因为我的项目标题比较短,所以当时没有发现文字抖动的问题,在github开源后,有很多的小伙伴跟我反馈这个问题,由于已经离职加入到新的项目中,一拖再拖到今天,真的非常抱歉。
如果你还不了解SlidingScaleTabLayout,可以先阅读:
仿陌陌选项卡:文字大小变化的SlidingScaleTabLayout
如果你想先了解优化后的用法,可以先查看github地址:
查看FlycoTabLayoutZ新用法
正文
文字抖动的原因
首先我们要知道为什么会出现这个抖动的问题。经过我的研究,发现问题出现在字体上,以下面为例:
原谅我的ps水平也就这个程度了。我们模拟一个文字变化的效果:
未选中文字大小为13sp,选中的文字大小为17sp;
当切换到50%时,文字应当都为15sp;
从上图可以看到,宽度并没有对齐,说明文字的大小变化并不是等比的,所以在文字大小频繁切换的时候,就出现了抖动的问题,且文字变化幅度越大,问题就越明显。
解决文字抖动的问题
我们已经知道了原因,就可以找到解决问题的办法。在Android上,显示元素主要就只有两种:文字和图片。既然文字我们无法控制,唯一的解决办法就是用图片。
解决的思路大体为:
1、在设置标题文字后,对于TextView生成图片副本;
2、隐藏标题文字,显示图片副本;
3、修改变化文字大小,变为图片大小;
4、如果选中的文字和未选中的文字样式不同(加粗,颜色),需要在选中后,刷新图片副本;
第一步:在设置标题文字后,对于TextView生成图片副本
为了尽量保证不失真,我们选择最大的文字大小生成副本:
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, Math.max(mTextSelectSize, mTextUnSelectSize));
然后对显示标题的TextView,生成图片副本:
public static Bitmap generateViewCacheBitmap(View view) {
view.destroyDrawingCache();
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(widthMeasureSpec, heightMeasureSpec);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
view.layout(0, 0, width, height);
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
// 请注意,必须要生成新的Bitmap
// ImageView内部有对DrawingCache回收的机制
return Bitmap.createBitmap(view.getDrawingCache());
}
第二步:隐藏标题文字,显示图片副本
// 显示图片副本
imageView.setImageBitmap(ViewUtils.generateViewCacheBitmap(textView));
// 保存最大宽度,主要是为了ViewPager滑动的时候,根据position计算显示的宽度
imageView.setMaxWidth(imageView.getDrawable().getIntrinsicWidth());
// 隐藏标题
textView.setVisibility(View.GONE);
第三步:修改变化文字大小,变为图片大小
之前改变文字大小的功能写在了TabScaleTransformer中:
private void changeTextSize(final TextView textView, final float position) {
// 字体大小相同,不需要切换
if (textSelectSize == textUnSelectSize) return;
// 必须要在View调用post更新样式,否则可能无效
textView.post(new Runnable() {
@Override
public void run() {
if (position >= -1 && position <= 1) { // [-1,1]
if (textSelectSize > textUnSelectSize) {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSelectSize - Math.abs((textSelectSize - textUnSelectSize) * position));
} else {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSelectSize + Math.abs((textUnSelectSize - textSelectSize) * position));
}
} else {
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textUnSelectSize);
}
}
});
}
我们修改为改变图片副本的大小:
private void changeDmgSize(ImageView imageView, float position) {
// 字体大小相同,不需要切换
if (textSelectSize == textUnSelectSize) return;
ViewGroup.LayoutParams params = imageView.getLayoutParams();
if (position >= -1 && position <= 1) { // [-1,1]
if (textSelectSize > textUnSelectSize) {
float scale = 1 - Math.abs((1 - minScale) * position);
params.width = (int) (imageView.getMaxWidth() * scale);
} else {
float scale = minScale + Math.abs((1 - minScale) * position);
params.width = (int) (imageView.getMaxWidth() * scale);
}
imageView.setLayoutParams(params);
} else {
int width;
if (textSelectSize > textUnSelectSize) {
width = (int) (imageView.getMaxWidth() * minScale);
} else {
width = imageView.getMaxWidth();
}
if (width != params.width) {
params.width = width;
imageView.setLayoutParams(params);
}
}
}
上面的代码都是计算规则,就没什么可说的了。
第四步:文字样式(加粗,颜色),在选中后,刷新图片副本
这一步和第一步几乎是一样的,只不过我们要加一些判断,防止无用的刷新:
// 如果选中的文字颜色和未选中的文字颜色不同,需要刷新
// 如果选中的是粗体,未选中是普通,也要刷新副本
if ((mTextSelectColor != mTextUnSelectColor || mTextBold == TEXT_BOLD_WHEN_SELECT)) {
tab_title.setVisibility(View.VISIBLE);
generateTitleDmg(tabView, tab_title);
}
第五步:一点点优化
如果你觉得开启副本的效果没有太大的变化,或者目前已经满足了你的需求,我建议关闭图片副本这个功能,毕竟文字的开销要比图片开销要小得多。
// 新增自定义属性
另外如果选中的文字和未选中的文字大小都是一样的,也没有必要开启图片副本,所以我增加了一些判断:
/**
* 如果文字的大小没有变化,不需要开启镜像,请注意
*/
private boolean isDmgOpen() {
return openDmg && mTextSelectSize != mTextUnSelectSize;
}
效果图对比
未开启图片副本:
开启图片副本:
总结
以上就是解决文字变化抖动的解决方案,如果在使用中大家遇到了哪些问题,希望大家继续反馈,感谢大家的支持。