Font size adaptive TextView-Tencent QMUI open source QMUIFontFitTextView control interpretation

To become a good Android development, a knowledge system is essential -

Preface

I need to make a TextView with a given width and an adaptive font size. I suddenly remembered that I had seen the open source Android UI framework of Tencent’s QMUI team-QMUI Android before. There was this control in it, and I also read its source code. I'm just interested, and didn't deliberately write it down. Now when I meet a demand, I will refer to the operation of the great gods again, and record it this time.

Source code interpretation

This control is called QMUIFontFitTextView, and the first is its constructor:

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
    }

There are two. The second one, when the AttributeSet attrs is set, the main operations done are:

Initialize the Paint object and set the maximum and minimum font size according to the screen density.

The next step is the key point, the refitText method, which is the key logic to realize font size adaptation.

/* 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);
    }

First look at the comment above, which means that the width must be set to a specific value. This is easy to understand. If the width can be changed, this control is meaningless.

In the code, first calculate the width of the text that can be displayed:

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

Then set the highest value and the lowest value, which are equivalent to the two endpoints of an interval:

float hi = maxSize;
float lo = minSize;

Next:

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

What is this for? Looking at the notes, personal understanding is to set a critical value for the spacing between words, which will be used later.

Then, the following logic is to first set the text size to the maximum value. If the width is smaller than the displayable width, then lo = maxSize; and finally display at this size, then why lo = maxSize;, we have to see At the end, when setting the size,

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

In other words, it is set based on the lo value, and the reason is that the comment says: Use lo so that we undershoot rather than overshoot

If the calculated width is greater than the displayable width after setting the size to the maximum value, then it needs to be re-adapted. First set the size to the minimum value, and then if it is less than the displayable width at this time, then you can zoom in on this basis, but ensure that it is within a certain range, this range is hi-lo)> threshold, and then

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

That is to take the middle value of the maximum and minimum values. If it is larger than the displayable width at this time, it means that it is too large and needs to be reduced again. Use this middle value as the maximum value, and then calculate the middle value with the minimum value. If it is still smaller than the displayable width, it is too small, continue to zoom in, take the middle value as the minimum value, and then calculate the middle value with the maximum value. Repeat until the condition is not met. The entire process is operated by the Paint object, and after calculating the appropriate size value, the font size of the TextView is set to this value.

The next step is to rewrite the onMeasure, onTextChanged, and onSizeChanged methods, and call the refitText method inside:

@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);
        }
    }

carry out.

At last

I am engaged in Android development by myself. I have been in the business for so long. I have also accumulated some treasured materials and shared them, hoping to help everyone improve their advanced level.

Share an Android learning PDF+architecture video+interview document+source notes collected by several big guys , advanced architecture technology advanced mind map, Android development interview topic materials, advanced advanced architecture materials

If you need it, you can learn PDF+architecture video+interview document+source notes for free on this Android

If you like this article, you might as well give me a thumbs-up, leave a message in the comment area or forward and support it~

Guess you like

Origin blog.csdn.net/River_ly/article/details/106668165