Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Have only one size for multiple auto-size TextViews

I have a layout with different boxes, each of them contains a bunch of TextViews in a similar layout.

I wish to use the auto-size feature of TextView, but each TextView only takes into account its own boundaries, and there is no way to enforce the same size on multiple auto-size TextViews that represent a similar element in a layout.

Ideally, I would like to be able to "chain" multiple TextView objects (located in completely different places), so the auto-size mechanism knows that they should all have the same text size (stick to minimum, since one text can be longer than the others).

like image 805
SirKnigget Avatar asked Sep 21 '18 12:09

SirKnigget


3 Answers

Updated:

I have developed a size aware TextView for your requirement. It notifies a listener when text size has changed. I have tested it and it works well. I hope it helps you.

SizeAwareTextView.java:

package com.aminography.textapp;

import android.content.Context;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;

public class SizeAwareTextView extends AppCompatTextView {

    private OnTextSizeChangedListener mOnTextSizeChangedListener;
    private float mLastTextSize;

    public SizeAwareTextView(Context context) {
        super(context);
        mLastTextSize = getTextSize();
    }

    public SizeAwareTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mLastTextSize = getTextSize();
    }

    public SizeAwareTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mLastTextSize = getTextSize();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (mLastTextSize != getTextSize()) {
            mLastTextSize = getTextSize();
            if (mOnTextSizeChangedListener != null) {
                mOnTextSizeChangedListener.onTextSizeChanged(this, mLastTextSize);
            }
        }
    }

    public void setOnTextSizeChangedListener(OnTextSizeChangedListener onTextSizeChangedListener) {
        mOnTextSizeChangedListener = onTextSizeChangedListener;
    }

    public interface OnTextSizeChangedListener {

        void onTextSizeChanged(SizeAwareTextView view, float textSize);
    }
}

MainActivity.java

package com.aminography.textapp;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.TypedValue;
import android.widget.EditText;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final SizeAwareTextView textView1 = findViewById(R.id.textView1);
        final SizeAwareTextView textView2 = findViewById(R.id.textView2);
        final SizeAwareTextView textView3 = findViewById(R.id.textView3);

        final List<SizeAwareTextView> textViewList = new ArrayList<>();
        textViewList.add(textView1);
        textViewList.add(textView2);
        textViewList.add(textView3);

        SizeAwareTextView.OnTextSizeChangedListener onTextSizeChangedListener = new SizeAwareTextView.OnTextSizeChangedListener() {
            @SuppressLint("RestrictedApi")
            @Override
            public void onTextSizeChanged(SizeAwareTextView view, float textSize) {
                for (SizeAwareTextView textView : textViewList) {
                    if (!textView.equals(view) && textView.getTextSize() != view.getTextSize()) {
                        textView.setAutoSizeTextTypeUniformWithPresetSizes(new int[]{(int) textSize}, TypedValue.COMPLEX_UNIT_PX);
                    }
                }
            }
        };

        for (SizeAwareTextView textView : textViewList) {
            textView.setOnTextSizeChangedListener(onTextSizeChangedListener);
        }

        ((EditText) findViewById(R.id.editText)).addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
            }

            @Override
            public void afterTextChanged(Editable editable) {
                textView1.setText(editable.toString());
            }
        });
    }

}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="top"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <com.aminography.textapp.SizeAwareTextView
        android:id="@+id/textView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#DEDEDE"
        android:text="Here is the first TextView"
        android:textSize="26sp"
        app:autoSizeMinTextSize="10sp"
        app:autoSizeStepGranularity="0.5sp"
        app:autoSizeTextType="uniform" />

    <com.aminography.textapp.SizeAwareTextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:background="#DEDEDE"
        android:text="Here is the second TextView"
        android:textSize="26sp"
        app:autoSizeMinTextSize="10sp"
        app:autoSizeStepGranularity="0.5sp"
        app:autoSizeTextType="uniform" />

    <com.aminography.textapp.SizeAwareTextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:background="#DEDEDE"
        android:text="Here is the third TextView"
        android:textSize="26sp"
        app:autoSizeMinTextSize="10sp"
        app:autoSizeStepGranularity="0.5sp"
        app:autoSizeTextType="uniform" />

    <android.support.v7.widget.AppCompatEditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="Here is the first TextView" />

</LinearLayout>

Final result:

enter image description here

like image 102
aminography Avatar answered Nov 15 '22 00:11

aminography


I modified this so that you can use it straight from XML without any code in Fragment or Activity

You need to add into values/attrs.xml the following snippet:

<declare-styleable name="SizeAwareTextView">
    <attr name="group" format="reference"/>
</declare-styleable>

Into values/arrays.xml declare the label ids which belong into the same group

<array name="labels">
    <item>@id/label1</item>
    <item>@id/label2</item>
    <item>@id/label3</item>
</array>

Then when declaring the view use group attribute to reference the labels:

        <SizeAwareTextView
            android:id="@+id/label1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="@color/white"
            android:textSize="14sp"
            android:textAllCaps="true"
            android:maxLines="1"
            app:autoSizeTextType="uniform"
            app:autoSizeMaxTextSize="15sp"
            app:group="@array/labels"
            android:text="@string/very_long_string"/>

Below is the modified SizeAwareTextView

class SizeAwareTextView: AppCompatTextView {

    private var lastTextSize: Float = 0F
    private var viewRefs: TypedArray? = null
    private var views = mutableListOf<SizeAwareTextView>()
    var onTextSizeChangedListener: OnTextSizeChangedListener? = object : OnTextSizeChangedListener {
        @SuppressLint("RestrictedApi")
        override fun onTextSizeChanged(view: SizeAwareTextView, textSize: Float) {
            resolveViews()
            views.forEach {
                if (view != it && view.textSize != it.textSize) {
                    it.setAutoSizeTextTypeUniformWithPresetSizes(intArrayOf(textSize.toInt()), TypedValue.COMPLEX_UNIT_PX)
                }
            }
        }
    }

    constructor(context: Context) : super(context) {
        lastTextSize = textSize
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        lastTextSize = textSize
        val a = context.obtainStyledAttributes(attrs, R.styleable.SizeAwareTextView)
        a.getResourceId(R.styleable.SizeAwareTextView_group, 0).let {
            if (it > 0) {
                viewRefs = resources.obtainTypedArray(it)
            }
        }
        a.recycle()
    }

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        lastTextSize = textSize
        val a = context.obtainStyledAttributes(attrs, R.styleable.SizeAwareTextView)
        a.getResourceId(R.styleable.SizeAwareTextView_group, 0).let {
            if (it > 0) {
                viewRefs = resources.obtainTypedArray(it)
            }
        }
        a.recycle()
    }

    fun resolveViews() {
        viewRefs?.let {
            var root = parent
            while (root.parent is View) {
                root = root.parent
            }
            for (i in 0 until it.length()) {
                val resId = it.getResourceId(i, 0)
                val v = (root as View).findViewById<SizeAwareTextView>(resId)
                if (v != null) {
                    views.add(v)
                } else {
                    Log.w(TAG, "Resource: $resId not found at idx: $i")
                }
            }
            it.recycle()
            viewRefs = null
        }
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        if (lastTextSize != textSize) {
            lastTextSize = textSize
            onTextSizeChangedListener?.onTextSizeChanged(this, lastTextSize)
        }
    }

    interface OnTextSizeChangedListener {
        fun onTextSizeChanged(view: SizeAwareTextView, textSize: Float)
    }

    companion object {
        val TAG = SizeAwareTextView::class.java.simpleName
    }
}
like image 27
hohteri Avatar answered Nov 15 '22 01:11

hohteri


This is a little bit different (I think) than what the OP may have been looking for, but what I needed was for a particular view that contained multiple TextView objects, once the layout had been determined to have the size of the smallest TextView become the size for all the TextViews. So I did this and put it in in the OnViewCreated() method of the fragment where my TextViews live:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            Timber.d("Lifecycle: In onViewCreated() of WelcomeFragment adjusting text fields");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            } else {
                //noinspection deprecation
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            }

            // Get all text views and find the smallest size and set them all to that
            int childCount = ((ViewGroup)view).getChildCount();
            float f = -1;
            ArrayList<AppCompatTextView> textViewArrayList = new ArrayList();
            for (int x = 0; x < childCount; x++) {
                View v = ((ViewGroup) view).getChildAt(x);
                if ( v instanceof androidx.appcompat.widget.AppCompatTextView) {
                    textViewArrayList.add((androidx.appcompat.widget.AppCompatTextView)v);
                    if ( f == -1) {
                        // Handle edge case - first TextView found initializes f
                        f = Math.max(f, ((androidx.appcompat.widget.AppCompatTextView) v).getTextSize());
                    } else {
                        f = Math.min(f, ((androidx.appcompat.widget.AppCompatTextView) v).getTextSize());
                    }
                }
            }
            int[] uniformSize = new int[]{(int) f};
            for (int x = 0; x < textViewArrayList.size(); x++) {
                TextViewCompat.setAutoSizeTextTypeUniformWithPresetSizes(textViewArrayList.get(x), uniformSize, TypedValue.COMPLEX_UNIT_PX);
            }
        }
    });
}
like image 22
tfrysinger Avatar answered Nov 14 '22 23:11

tfrysinger