SlidingScaleTabLayout(2):解决标题文字变化抖动的问题

前言

之前我在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);
}

第五步:一点点优化

如果你觉得开启副本的效果没有太大的变化,或者目前已经满足了你的需求,我建议关闭图片副本这个功能,毕竟文字的开销要比图片开销要小得多。

// 新增自定义属性
<!-- 是否开启文字的图片镜像 -->
<attr name="tl_openTextDmg" format="boolean"/>

另外如果选中的文字和未选中的文字大小都是一样的,也没有必要开启图片副本,所以我增加了一些判断:

/**
* 如果文字的大小没有变化,不需要开启镜像,请注意
*/
private boolean isDmgOpen() {
    return openDmg && mTextSelectSize != mTextUnSelectSize;
}

效果图对比

未开启图片副本:
未开启图片副本
开启图片副本:
在这里插入图片描述

总结

以上就是解决文字变化抖动的解决方案,如果在使用中大家遇到了哪些问题,希望大家继续反馈,感谢大家的支持。

发布了116 篇原创文章 · 获赞 61 · 访问量 25万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 代码科技 设计师: Amelia_0503

分享到微信朋友圈

×

扫一扫,手机浏览