Autosizing TextViews in RecyclerView causes text size to decrease

8k views Asked by At

I am trying to use Autosizing TextViews in a RecyclerView, but when I scroll a few times the text gets so small that it's obviously not working properly.

Example of my TextView:

<android.support.v7.widget.AppCompatTextView
        android:id="@+id/textview_unit_title"
        android:layout_width="@dimen/tile_image_size"
        android:layout_height="wrap_content"
        android:maxLines="2"
        android:textSize="@dimen/medium_size"
        android:textColor="@color/color_text"
        android:paddingTop="@dimen/padding_title"
        android:layout_marginRight="2dp"
        android:layout_marginEnd="2dp"
        app:autoSizeMaxTextSize="@dimen/style_medium"
        app:autoSizeTextType="uniform"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintLeft_toRightOf="@id/imageview_unit_icon"
        app:layout_constraintTop_toTopOf="parent"/>

Should I update this scaling somewhere else programmatically or is there another solution?

7

There are 7 answers

1
IntelliJ Amiya On BEST ANSWER

Autosizing TextViews

Android 8.0 (API level 26) allows you to instruct a TextView to let the text size expand or contract automatically to fill its layout based on the TextView's characteristics and boundaries.

Note: If you set autosizing in an XML file, it is not recommended to use the value "wrap_content" for the layout_width or layout_height attributes of a TextView. It may produce unexpected results.

You should bound height

 android:layout_height="30dp"
0
Joe Wong On

I only just set android:maxLines="1" in xml file, then code in bindViewHolder

  TextViewCompat.setAutoSizeTextTypeWithDefaults(binding.tvResultExplain, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
  binding.tvResultExplain.setText("");
  TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(binding.tvResultExplain, 12,
                16, 1, TypedValue.COMPLEX_UNIT_SP);
  binding.tvResultExplain.setText(item.getStatusExplain());

It works for me, maybe it can resolve your situation as well.

1
Pavel Haluza On

I packaged Michael Celey's answer into a class. The parameters app:autoSizeMinTextSize, app:autoSizeMaxTextSize, app:autoSizeTextType are taken from xml.

public class AutosizingTextView extends AppCompatTextView {

    private int minTextSize;
    private int maxTextSize;
    private int granularity;

    public AutosizingTextView(Context context) {
        super(context);
        init();
    }

    public AutosizingTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AutosizingTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
        maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
        granularity = TextViewCompat.getAutoSizeStepGranularity(this);
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        // this method is called on every setText
        disableAutosizing();
        super.setText(text, type);
        post(this::enableAutosizing); // enable after the view is laid out and measured at max text size
    }

    private void disableAutosizing() {
        TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
    }

    private void enableAutosizing() {
        TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this,
                minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
    }
}```
1
Michael Celey On

The issue I've seen with this is that setting your view height to be wrap_content allows the text size to get smaller, but the text will never get bigger again. This is why the documentation recommends to not use wrap_content for the view size. However, I've found that if you turn off the auto-resizing, set the text size to whatever the max is, then re-enable auto-resizing, the text size resets to the largest size and scales down as necessary.

So my view in XML would look like:

<android.support.v7.widget.AppCompatTextView
    android:id="@+id/text_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:ellipsize="end"
    android:textAllCaps="true"
    android:textColor="@android:color/white"
    android:textSize="42sp"
    app:autoSizeMinTextSize="26dp"
    app:autoSizeMaxTextSize="42dp"
    app:autoSizeTextType="none"/>

Then in my ViewHolder when I bind my text to the view:

TextView title = view.findViewById(R.id.text_title);
String titleValue = "Some Title Value";

// Turn off auto-sizing text.
TextViewCompat.setAutoSizeTextTypeWithDefaults(title, 
    TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);

// Bump text size back up to the max value.
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 42);

// Set your text as normal.
title.setText(titleValue);

// Post a runnable to re-enable auto-sizing text so that it occurs
// after the view is laid out and measured at max text size.
title.post(new Runnable() {
    @Override
    public void run() {     
        TextViewCompat
            .setAutoSizeTextTypeUniformWithConfiguration(title,
                    26, 42, 1, TypedValue.COMPLEX_UNIT_DIP);
    }
});
0
Peter On

Just .setText("") before resetting the text size you want. That ensures that you are not setting the textsize and then immediately autoresizing using the previous text value in the TextView. Like this:

TextView wordWordTextView = getView().findViewById(R.id.wordWordTextView);
wordWordTextView.setAlpha(0.0f);
wordWordTextView.setText("");
wordWordTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 50);
wordWordTextView.setText(wordStr);
wordWordTextView.animate().alpha(1.0f).setDuration(250);
0
SteelBytes On

the above solutions didn't work for me so here's mine

public class MyTextView extends AppCompatTextView {
    ...
    @Override
    public final void setText(CharSequence text, BufferType type) {
        // work around stupid auto size text not *growing* the font size we re binding in a RecyclerView if previous bind caused a small font
        int minTextSize = 0, maxTextSize = 0, granularity = 0;
        boolean doHack = TextViewCompat.getAutoSizeTextType(this) != TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
        if (doHack) {
            minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
            maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
            if (minTextSize <= 0 || maxTextSize <= minTextSize) { // better than validateAndSetAutoSizeTextTypeUniformConfiguration crashing
                if (BuildConfig.DEBUG)
                    throw new AssertionError("fix ya layout");
                doHack = false;
            } else {
                granularity = TextViewCompat.getAutoSizeStepGranularity(this);
                if (granularity < 0)
                    granularity = 1; // need this else setAutoSizeTextTypeUniformWithConfiguration barfs. TextView.UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = 1.
                // make the TextView have 0 size so setAutoSizeTextTypeUniformWithConfiguration won't do calculations until after a layout pass using maxSize
                TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
                setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);
                measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY));
                setRight(getLeft());
                setBottom(getTop());
                requestLayout();
            }
        }
        super.setText(text, type);
        if (doHack)
            TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this, minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
    }
    ...
}
3
Oscar Fong On

Pavel Haluza's answer's approach was great. However, it didn't work, probably because he missed a line setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);.

Here is my updated version:

public class MyTextView extends AppCompatTextView {

private int minTextSize;
private int maxTextSize;
private int granularity;

public MyTextView(Context context) {
    super(context);
    init();
}

public MyTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

public MyTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}

private void init() {
    minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
    maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
    granularity = Math.max(1, TextViewCompat.getAutoSizeStepGranularity(this));
}

@Override
public void setText(CharSequence text, BufferType type) {
    // this method is called on every setText
    disableAutoSizing();
    setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);
    super.setText(text, type);
    post(this::enableAutoSizing); // enable after the view is laid out and measured at max text size
}

private void disableAutoSizing() {
    TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
}

private void enableAutoSizing() {
    TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this,
            minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
}}