Autosizing EditText
Asked Answered
J

6

23

Android recently added the support for resizing TextViews text size based on the view size and the min and max text size.
https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html

Unfortunately, they don't support EditTexts, so Is there any other alternatives for EditText?

Jareb answered 1/11, 2017 at 11:31 Comment(1)
github.com/ViksaaSkool/AutoFitEditText ?Tayler
H
10

I was stuck as you, how EditText is child of TextView but don't support autosize ¿?¿?

I have achieve this with some kind of hack. First I saw the TextView code to copy and implement as extension (in Kotlin) on EditTextView, but.. there is a bulk of methods, so endly I discard that option.

What I have do, it's to use a TextView invisible (yes I know is a complete hack, am not very happy with that but Google should be ashamed about this)

This is my xmls

    <TextView android:id="@+id/invisibleTextView"
    android:layout_height="0dp"
    android:layout_width="match_parent"
    android:focusable="false"
    app:autoSizeTextType="uniform"
    app:autoSizeMinTextSize="@dimen/text_min"
    app:autoSizeMaxTextSize="@dimen/text_max"
    app:autoSizeStepGranularity="@dimen/text_step"
    android:textAlignment="center"
    app:layout_constraintLeft_toLeftOf="@id/main"
    app:layout_constraintRight_toRightOf="@id/main"
    app:layout_constraintTop_toBottomOf="@id/textCount"
    app:layout_constraintBottom_toBottomOf="@id/main"
    android:visibility="invisible"
    tool:text="This is a Resizable Textview" />


<EditText android:id="@+id/resizableEditText"
    android:layout_height="0dp"
    android:layout_width="match_parent"
    android:textAlignment="center"
    app:layout_constraintLeft_toLeftOf="@id/main"
    app:layout_constraintRight_toRightOf="@id/main"
    app:layout_constraintTop_toBottomOf="@id/textCount"
    app:layout_constraintBottom_toBottomOf="@id/main"
    android:maxLength="@integer/max_text_length"
    tool:text="This is a Resizable EditTextView" />

Note: It's important both view have the same width/height

Then on my code I use the autocalculations from this textview to use on my EditTextView.

private fun setupAutoresize() {
    invisibleTextView.setText("a", TextView.BufferType.EDITABLE) //calculate the right size for one character
    resizableEditText.textSize = autosizeText(invisibleTextView.textSize)
    resizableEditText.setHint(R.string.text_hint)

    resizableEditText.addTextChangedListener(object : TextWatcher {
        override fun afterTextChanged(editable: Editable?) {
            resizableEditText.textSize = autosizeText(invisibleTextView.textSize)
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
            textCount.text = currentCharacters.toString()
            val text = if (s?.isEmpty() ?: true) getString(R.string.text_hint) else s.toString()
            invisibleTextView.setText(text, TextView.BufferType.EDITABLE)
        }
    })
}

private fun autosizeText(size: Float): Float {
    return size / (resources.displayMetrics.density + MARGIN_FACTOR /*0.2f*/)
}

As note, for change the size of hint i use this Android EditText Hint Size.

I know it's a hard workaround, but at least we are sure this will continue working even when resizable change on future versions, while a propetary or abandoned github lib will fail.

I hope some day, google hear us and implement on childs this wonderfull feature, and we could avoid all this stuff

Hope this helps

Hobnailed answered 19/1, 2018 at 8:50 Comment(0)
C
2

This library is based on:

AutoFitTextView https://github.com/ViksaaSkool/AutoFitEditText Please try this one

Creamery answered 12/3, 2018 at 13:49 Comment(1)
This is a horrible library!Lonnylonslesaunier
L
1

You can use a RelativeLayout to hide a TextView behind an EditText, both of which will have equal height and width. The TextView will have android:autoSizeTextType="uniform" By using setOnTextChangedListener on the EditText, you can set the text of the TextView to whatever is in the EditText. Then the text size of the TextView will be adjusted automatically. Then you have to set the text size of the EditText as same as that of the TextView. Here is the layout:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"

android:layout_height="match_parent" tools: context=".MainActivity">

<TextView

android:id="@+id/test"

android:layout_width="match_parent" android:layout_height="match_parent" android:padding="0dp"

android:layout_margin="0dp"

android: autoSizeTextType="uniform"

android: autosizeMaxTextSize="500sp" android: autosizestepGranularity="1sp"/>

<EditText

android:id="@+id/edit"

android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="0dp"/>

android:padding="0dp"

</RelativeLayout>

And the code:

public void code(){

edit.setMovement Method (null);

edit.addTextChangedListener(new Textwatcher() {

@Override

public void onTextChanged (CharSequence s, int start, int before, int count) { test.setText (edit.getText().tostring()); edit.setTextSize(pixel2dip(test.getTextSize()));

}

public static float pixel2dip(float a) {

int b = (int) (a);

int c = (int) (b / Resources.getSystem().getDisplayMetrics ().scaledDensity); return (float) (c);

});

}
Laverne answered 17/7, 2021 at 15:30 Comment(0)
T
0

Here is an option for single-line EditText views that computes the text width after every change and scales up/down the textScale as needed, adhering to a max and min size.

Perhaps someone can improve it to have it work for multiline EditTexts.

// set up auto-text-size
val maxTextScale = binding.editText.textSize
val minTextScale = 0.2 * maxTextScale // ensure text doesn't get too small.
binding.editText.addTextChangedListener(object : TextWatcher {
    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        val paint = TextPaint(binding.editText.paint)
        val desiredTextWidth = StaticLayout.getDesiredWidth(s, paint)

        // added so that the text isn't slightly too big
        val ensureWiggleRoom = 0.95F
        val scaleFactor = binding.editText.width / desiredTextWidth
        val candidateTextSize = truncate(binding.editText.textSize * scaleFactor * ensureWiggleRoom)
        if (candidateTextSize > minTextScale && candidateTextSize < maxTextScale) {
            binding.editText.setTextSize(TypedValue.COMPLEX_UNIT_PX, candidateTextSize)
        }
    }

    override fun afterTextChanged(s: Editable?) {}
})
Tinatinamou answered 9/11, 2021 at 19:24 Comment(0)
A
0

It works for me. In xml add:

<EditText
            android:id="@+id/editTextMsg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="0.1"
            android:background="@drawable/edit_corner_white"
            android:gravity="start|top"
            android:inputType="textMultiLine"
            android:minLines="1"
            android:lines="1"
            android:maxLines="20"
            android:textSize="18dp"
            android:padding="10dp"
            android:hint="@string/napisz_cos"
            android:imeOptions="actionSend"
            />

and then in activity code:

 editTextMsg.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {


                int div   = editTextMsg.getText().toString().length() % 25;   //max 25chars in line

                if(div == 0 ) {
                    String tx = editTextMsg.getText().toString().trim() + "\n";
                    editTextMsg.setText(tx);

                    int pxH = (int)( editTextMsg.getLineCount() * 24 * getResources().getDisplayMetrics().density);

                    editTextMsg.setHeight (pxH );

                    //set cursor to end of text..
                    editTextMsg.setSelection(editTextMsg.length());
                }
            }
        });
Arbitress answered 28/10, 2022 at 7:14 Comment(0)
D
-1

You can try my AutoSizeEditText:

   /**
 * Wrapper class for {@link EditText}.
 * It helps to achieve auto size behaviour which exists in {@link AppCompatTextView}.
 * The main idea of getting target text size is measuring {@link AppCompatTextView} and then
 * extracting from it text size and then applying extracted text size to target {@link EditText}.
 */
public class AutoSizeEditText extends FrameLayout {

    private static final String TEST_SYMBOL = "T";

    private static final boolean TEST = false;

    /**
     * Vertical margin which is applied by default in {@link EditText} in
     * comparison to {@link AppCompatTextView}
     */
    private static final float VERTICAL_MARGIN = convertDpToPixel(4);

    /**
     * {@link TextMeasure} which helps to get target text size for {@link #wrappedEditTex}
     * via its auto size behaviour.
     */
    @NonNull
    private final TextMeasure textMeasurer;

    /**
     * {@link AppCompatEditText} we want to show to the user
     */
    @NonNull
    private final EditText wrappedEditTex;

    public AutoSizeEditText(Context context) {
        this(context, null);
    }

    public AutoSizeEditText(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoSizeEditText(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        // don't clip children
        setClipChildren(false);
        setClipToOutline(false);
        setClipToPadding(false);

        // using AttributeSet of TextView in order to apply it our text views
        wrappedEditTex = createWrappedEditText(context, attrs);

        textMeasurer = createTextMeasure(context, attrs, wrappedEditTex);

        addView(wrappedEditTex, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        addView(textMeasurer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @NonNull
    private TextMeasure createTextMeasure(Context context, AttributeSet attrs, EditText editText) {
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.AutoSizeEditText);
        final int minSize = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeMinTextSize, convertDpToPixel(10));
        final int maxSize = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeMaxTextSize, convertDpToPixel(18));
        final int step = (int) typedArray.getDimension(R.styleable.AutoSizeEditText_autoSizeStepGranularity, convertDpToPixel(1));
        typedArray.recycle();

        TextMeasure textMeasurer = new TextMeasure(context);
        final Editable text = editText.getText();
        final CharSequence hint = editText.getHint();
        if (!TextUtils.isEmpty(text)) {
            textMeasurer.setText(text);
        } else if (!TextUtils.isEmpty(hint)) {
            textMeasurer.setText(hint);
        } else {
            textMeasurer.setText(TEST_SYMBOL);
        }

        TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(
                textMeasurer, minSize, maxSize, step, TypedValue.COMPLEX_UNIT_PX);

        textMeasurer.setVisibility(View.INVISIBLE);
        textMeasurer.setPadding(0, 0, 0, 0);
        if (TEST) {
            textMeasurer.setTextColor(Color.RED);
            final ColorDrawable background = new ColorDrawable(Color.YELLOW);
            background.setAlpha(50);
            textMeasurer.setBackground(background);
            textMeasurer.setAlpha(0.2f);
            textMeasurer.setVisibility(View.VISIBLE);
        }
        return textMeasurer;
    }

    /**
     * Creating {@link EditText} which user will use and see
     *
     * @param attrs {@link AttributeSet} which comes from most likely from xml, which can be user for {@link EditText}
     *              if attributes of {@link TextView} were declared in xml
     * @return created {@link EditText}
     */
    @NonNull
    protected EditText createWrappedEditText(Context context, AttributeSet attrs) {
        return new AppCompatEditText(context, attrs);
    }

    @NonNull
    public EditText getWrappedEditTex() {
        return wrappedEditTex;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        wrappedEditTex.measure(
                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

        final int targetHeight = (int) (height
                - VERTICAL_MARGIN * 2
                - wrappedEditTex.getPaddingTop()
                - wrappedEditTex.getPaddingBottom());

        final int targetWidth = (width
                - wrappedEditTex.getTotalPaddingStart()
                - wrappedEditTex.getTotalPaddingEnd());

        textMeasurer.measure(
                MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(targetHeight, MeasureSpec.EXACTLY)
        );

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        final int layoutHeight = getMeasuredHeight();
        final int layoutWidth = getMeasuredWidth();

        wrappedEditTex.layout(0, 0, layoutWidth, layoutHeight);

        if (changed) {
            final int measuredHeight = textMeasurer.getMeasuredHeight();
            final int measuredWidth = textMeasurer.getMeasuredWidth();
            final int topCoordinate = (int) (wrappedEditTex.getPaddingTop() + VERTICAL_MARGIN);
            final int leftCoordinate = wrappedEditTex.getTotalPaddingStart();

            textMeasurer.layout(
                    leftCoordinate,
                    topCoordinate,
                    measuredWidth + leftCoordinate,
                    topCoordinate + measuredHeight);

            wrappedEditTex.setTextSize(TypedValue.COMPLEX_UNIT_PX, textMeasurer.getTextSize());
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return wrappedEditTex.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return wrappedEditTex.onTouchEvent(event);
    }

    /**
     * Adjust text size due to the fact we want hint to be always visible
     *
     * @param hint Hint for {@link #wrappedEditTex}
     */
    public void setHint(CharSequence hint) {
        wrappedEditTex.setHint(hint);
        textMeasurer.setText(hint);
    }

    /**
     * Adjust text size for TypeFace, because it can change calculations
     *
     * @param typeface for {@link #wrappedEditTex}
     */
    public void setTypeface(Typeface typeface) {
        wrappedEditTex.setTypeface(typeface);
        textMeasurer.setTypeface(typeface);
    }

    public void setTextColor(Integer textColor) {
        wrappedEditTex.setTextColor(textColor);
    }

    public void setHintTextColor(Integer hintTextColor) {
        wrappedEditTex.setHintTextColor(hintTextColor);
    }

    public void setText(CharSequence text) {
        wrappedEditTex.setText(text);
    }

    private static class TextMeasure extends AppCompatTextView {

        public TextMeasure(Context context) {
            super(context);
        }

        @Override
        public void setInputType(int type) {

        }

        @Override
        public void setRawInputType(int type) {

        }

        @Override
        public int getInputType() {
            return EditorInfo.TYPE_NULL;
        }

        @Override
        public int getMaxLines() {
            return 1;
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            return true;
        }

        @Override
        public int getMinLines() {
            return 1;
        }
    }
}

Example of using my component as below:

<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

    <com.vladislavkarpman.autosizeedittext.AutoSizeEditText
        android:layout_width="300dp"
        android:layout_height="100dp"
        app:autoSizeMaxTextSize="50dp"
        app:autoSizeMinTextSize="4dp"
        app:autoSizeStepGranularity="1dp"
        app:autoSizeTextType="uniform"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
Donetta answered 31/7, 2019 at 16:50 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.