美文网首页Android高级进阶Android开发Android开发经验谈
字体大小自适应TextView-腾讯QMUI开源QMUIFont

字体大小自适应TextView-腾讯QMUI开源QMUIFont

作者: 初壹十五a | 来源:发表于2020-06-10 15:44 被阅读0次

    要想成为一名优秀的Android开发,一份 知识体系 是必不可少的~

    前言

    需要做一个宽度给定的情况下,字体大小自适应的TextView,突然想起之前看过腾讯QMUI团队开源的Android UI框架——QMUI Android,里面就有这个控件,也看过它的源码,但是当时只是感兴趣,并没有刻意记下来,现在遇到需求了,就再去参考参考大神们的操作,这次就记录下来。

    源码解读

    这个控件叫做QMUIFontFitTextView,首先是它的构造函数:

    public QMUIFontFitTextView(Context context) {
            this(context, null);
        }
    
        public QMUIFontFitTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
    
            mTestPaint = new Paint();
            mTestPaint.set(this.getPaint());
    
            TypedArray array = context.obtainStyledAttributes(attrs,
                    R.styleable.QMUIFontFitTextView);
            minSize = array.getDimensionPixelSize(
                    R.styleable.QMUIFontFitTextView_qmui_minTextSize, Math.round(14 * QMUIDisplayHelper.DENSITY));
            maxSize = array.getDimensionPixelSize(
                    R.styleable.QMUIFontFitTextView_qmui_maxTextSize, Math.round(18 * QMUIDisplayHelper.DENSITY));
            array.recycle();
            //max size defaults to the initially specified text size unless it is too small
        }
    

    有两个,第二个在设置了AttributeSet attrs的情况下,所做的操作主要有:

    初始化Paint对象、根据屏幕密度设置字体大小的最大值和最小值。

    接下来就是重点,refitText方法,这个方法就是实现字体大小自适应的关键逻辑。

    /* Re size the font so the specified text fits in the text box
         * assuming the text box is the specified width.
         */
        private void refitText(String text, int textWidth) {
            if (textWidth <= 0)
                return;
            int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
            float hi = maxSize;
            float lo = minSize;
            float size;
            final float threshold = 0.5f; // How close we have to be
    
            mTestPaint.set(this.getPaint());
    
            mTestPaint.setTextSize(maxSize);
            if(mTestPaint.measureText(text) <= targetWidth) {
                lo = maxSize;
            } else {
                mTestPaint.setTextSize(minSize);
                if(mTestPaint.measureText(text) < targetWidth) {
                    while((hi - lo) > threshold) {
                        size = (hi+lo)/2;
                        mTestPaint.setTextSize(size);
                        if(mTestPaint.measureText(text) >= targetWidth)
                            hi = size; // too big
                        else
                            lo = size; // too small
                    }
                }
            }
    
            // Use lo so that we undershoot rather than overshoot
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
        }
    

    首先看上面的注释,意思是说宽度一定要设置成具体值,这个很好理解,要是宽度可以变化,这个控件就没有意义了。

    代码中,首先算出可以显示文字的宽度:

    int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
    

    然后设置最高值和最低值,相当于一个区间的两个端点:

    float hi = maxSize;
    float lo = minSize;
    

    接下来:

    final float threshold = 0.5f; // How close we have to be
    

    这个是干什么的呢?看注释,个人理解就是设置字与字之间间距的一个临界值,后面会用到。

    然后,下面的逻辑就是,先把文字大小设置成最大值,如果这个宽度比可显示宽度小,那就lo = maxSize;,并最终以这个大小显示,那为什么lo = maxSize;呢,我们要看到最后,设置大小的时候,

    // Use lo so that we undershoot rather than overshoot
    this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    

    也就是说,是以lo值去设置的,原因就是注释说的:Use lo so that we undershoot rather than overshoot

    如果把大小设置成最大值之后,计算出来的宽度大于可显示宽度,那么就需要重新适配。先把大小设置成最小值,然后如果这时候小于可显示宽度,那么就可以在这个基础上进行放大,但是要保证在一定的范围内,这个范围就是hi - lo) > threshold,然后

    size = (hi+lo)/2;
    mTestPaint.setTextSize(size);
    

    也就是取最大最小值的中间值,如果这时候又大于可显示宽度了,就是放太大了,就又需要缩小一点,把这个中间值作为最大值,再去跟最小值算中间值。如果还是小于可显示宽度,那就是放太小了,继续放大,把中间值作为最小值,再去跟最大值算中间值。循环进行,直到条件不满足。整个过程都是通过Paint对象去操作,算出合适的大小值之后再把TextView的字体大小设置成这个值。

    接下来就是重写onMeasure、onTextChanged、onSizeChanged方法,并在里面调用refitText方法:

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
            int height = getMeasuredHeight();
            refitText(this.getText().toString(), parentWidth);
            this.setMeasuredDimension(parentWidth, height);
        }
    
        @Override
        protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
            refitText(text.toString(), this.getWidth());
        }
    
        @Override
        protected void onSizeChanged (int w, int h, int oldw, int oldh) {
            if (w != oldw) {
                refitText(this.getText().toString(), w);
            }
        }
    

    完成。

    最后

    我自己从事 Android 开发,从业这么久,我也积累了一些珍藏的资料,分享出来,希望可以帮助到大家提升进阶

    分享一份由几位大佬一起收录整理的Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料

    如果你有需要的话,可以在这Android学习PDF+架构视频+面试文档+源码笔记免费领取

    喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗~

    相关文章

      网友评论

        本文标题:字体大小自适应TextView-腾讯QMUI开源QMUIFont

        本文链接:https://www.haomeiwen.com/subject/crlptktx.html