Как автоматически настроить размер текста в многострочном TextView в соответствии с максимальными размерами вида?

Я искал способ автоматического подгонки текста внутри textview. Через мой поиск я нашел много решений, таких как:

но, как и многие другие, это не решает мою проблему. Они не работают так, как ожидалось, когда мы используем TextView с многострочным.

в основном моя цель-это один:

Phase 1Phase 2Phase 3Phase 4

Как вы можете видеть, текст изменяется в зависимости от ширины, высоты, а также обращает внимание на разрыв строки, создавая многострочный textview. Также можно изменить шрифт.

одна из моих идей, чтобы решить это что-то вроде этого:

int size = CONSTANT_MAX_SIZE;
TextView tv = (TextView) findViewById(R.id.textview1)
while(Math.abs(tv.getMeasuredHeight()) >= TEXTVIEW_MAX_HEIGHT) {
    size--;
    tv.setTextSize(size);
    tv.measure(MeasureSpec.UNSPECIFIED,MeasureSpec.UNSPECIFIED);
    i++;
}

CONSTANT_MAX_SIZE-константа, определяющая максимальный размер шрифта (свойство textsize в TextView)

TEXTVIEW_MAX_HEIGHT постоянная это определяет максимальный размер, который может иметь textview.

это вызывалось каждый раз при изменении текста в textview.

TextView xml был чем-то вроде этого:

<TextView
     android:id="@+id/textview1"
     android:layout_width="200dp"
     android:layout_height="wrap_content"
     android:singleLine="false"
     android:inputType="textMultiLine"
     android:text=""
     android:textSize="150sp" />

поскольку ширина будет ограничена в XML, необходимо учитывать только высоту представления, так как при необходимости настройка android автоматически создаст многострочную строку.

хотя это потенциальное решение не работает отлично (далеко от него), и это не поддерживает изменение размера вниз (при удалении текста).

любые предложения и/или идеи?

6 ответов


самое близкое решение этой проблемы было основано на методе из paint.

в основном paint имеет метод под названием "breaktext", который:

общественные инт breakText (объект CharSequence текст, начать инт, инт конец, логическое measureForwards, float maxWidth, float[] measuredWidth)

добавлено в API уровня 1

измерьте текст, останавливая раньше если измеренная ширина превышает значение maxwidth. Возвращает количество символов, которые были измерены, и если measuredWidth не является нулевым, возвращает в нем фактическую измеренную ширину.

я объединяю это с краской "getTextBounds", которая:

public void getTextBounds (текст строки, int start, int end, Rect bounds)

добавлено в API уровня 1

возврат в пределах (выделенных вызывающим) наименьший прямоугольник, который охватывает все >символы, с подразумеваемым началом в точке (0,0).

Итак, теперь я могу получить количество символов, которые соответствуют заданной ширине и высоте этих символов.

используя некоторое время, вы можете продолжать перемещать удаляющие символы из строки, которую вы хотите измерить, и получить количество строк (используя while(index

дополнительно вы необходимо добавить переменную высоту для каждых двух строк, представляющих пространство между строками (которое не учитывается в getTextBounds).

в качестве примера кода функция для определения высоты текста с несколькими строками выглядит примерно так:

public int getHeightOfMultiLineText(String text,int textSize, int maxWidth) {
    paint = new TextPaint();
    paint.setTextSize(textSize);
    int index = 0;
    int linecount = 0;
    while(index < text.length()) {
        index += paint.breakText(text,index,text.length,true,maxWidth,null);
        linecount++;
    }

    Rect bounds = new Rect();
    paint.getTextBounds("Yy", 0, 2, bounds);
    // obtain space between lines
    double lineSpacing = Math.max(0,((lineCount - 1) * bounds.height()*0.25)); 

    return (int)Math.floor(lineSpacing + lineCount * bounds.height());

Примечание: переменная maxWidth находится в пикселях

затем вам придется вызвать этот метод через некоторое время, чтобы определить, какой максимальный размер шрифта для этой высоты. Пример кода быть:

textSize = 100;
int maxHeight = 50;
while(getHeightOfMultiLineText(text,textSize,maxWidth) > maxHeight)
  textSize--;

к сожалению, это был единственный (насколько я знаю), как я смог добиться от изображения выше.

надеюсь, что это может помочь любому, кто пытается преодолеть это препятствие.


у вас есть довольно хорошее решение на эту проблему, но я взглянул на это с другой точки зрения. Я думаю, что логика проще и понятнее, по крайней мере, для новых разработчиков android. Идея основана на вставке символа "\n " между пробелами большой строки. Затем я устанавливаю отредактированную строку в textView в качестве отображаемого текста. Итак, вот код..

private String insertNewLineCharAtString(String str, int step){
    StringBuilder sb = new StringBuilder();
    int spaceCounter = 0;
    String[] strArray = str.split(" ");
    for(int i = 0; i < strArray.length; i++){



        if(spaceCounter == step){
            sb.append('\n');
            sb.append(strArray[i]);
            spaceCounter = 0;
        }else{
            sb.append(" "+strArray[i]);
        }
        spaceCounter++;


    }
    return sb.toString();
}

параметры просты. Str-это строка, которую мы будем редактировать и шаг переменной определяет, во сколько пробелов метод вставит символ' /n'. Потом , вы просто называете что-то вроде этого :

myTextView.setText(insertNewLineCharactersAtString(yourDisplayingStr);

надеюсь, это поможет многим из вас , что вы не хотите углубляться с TextPaint, Rects и математическими функциями..


спасибо за ваше решение, вы не супермен! Я реализовал ваш код в производном классе TextView. Код преобразуется в код C# для Xamarin android. Вы можете легко конвертировать в Java, если вам нужно(удалить первый конструктор для Java).

public class AutoFitTextView : TextView
{
    public AutoFitTextView(System.IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer)
        : base(javaReference, transfer)
    {
    }

    public AutoFitTextView(Context context)
        : base(context)
    {

    }

    public AutoFitTextView(Context context, IAttributeSet attrs)
        : base(context, attrs)
    {

    }


    public void ResizeFontSize()
    {
        int textSize = 50;
        int maxHeight = this.Height;
        while (GetHeightOfMultiLineText(this.Text, textSize, this.Width) > maxHeight)
        {
            textSize--;
        }
        float scaleFactor = Context.Resources.DisplayMetrics.ScaledDensity;
        float additionalFactor = 1.2f;

        TextSize = ((float)(textSize / (additionalFactor * scaleFactor)));
    }


    private int GetHeightOfMultiLineText(string text, int textSize, int maxWidth)
    {
        TextPaint paint = new TextPaint();
        paint.TextSize = textSize;
        int index = 0;
        int lineCount = 0;
        while (index < text.Length)
        {
            index += paint.BreakText(text, index, text.Length, true, maxWidth, null);
            lineCount++;
        }

        Rect bounds = new Rect();
        paint.GetTextBounds("Yy", 0, 2, bounds);
        // obtain space between lines
        double lineSpacing = Math.Max(0, ((lineCount - 1) * bounds.Height() * 0.25));

        return (int)Math.Floor(lineSpacing + lineCount * bounds.Height());
    }

}

вот код AXML

        <touchtest.AutoFitTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/tvLabel2"
        android:singleLine="false"
        android:inputType="textMultiLine"
        android:text="I am here To test this text" />

enter image description here enter image description here enter image description here

немного улучшенный ответ phamducgiam. Он показывает текст с уже установленным размером, а не skretchs после показа. Выглядит уродливо в редакторе из-за запуска textSize 100, но работает так, как должно в run. Вот код:

import android.content.Context;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

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

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

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

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // start decreasing font size from 100
        setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);

        // calculate dimensions. don't forget about padding
        final float maxWidth = getWidth()-(getPaddingLeft()+getPaddingRight());
        final float maxHeight = getHeight()-(getPaddingTop()+getPaddingBottom());

        if (maxWidth < 1.0f || maxHeight < 1.0f) {
            return;
        }

        CharSequence text = getText();
        int lineCount = getLineCount(maxWidth, text);
        float height = getHeight(lineCount);
        // keep decreasing font size until it fits
        while (height > maxHeight) {
            final float textSize = getTextSize();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
            height = getHeight(getLineCount(maxWidth, getText()));
        }
        // show fitted textView
        requestLayout();
    }

    private float getHeight(int lineCount) {
        return lineCount * getLineHeight() + (lineCount > 0 ? (lineCount - 1) * getPaint().getFontSpacing() : 0);
    }

    private int getLineCount(float maxWidth, CharSequence text) {
        int lineCount = 0;
        int index = 0;
        final TextPaint paint = getPaint();
        while (index < text.length()) {
            index += paint.breakText(text, index, text.length(), true, maxWidth, null);
            lineCount++;
        }
        return lineCount;
    }
}

С помощью идея от nunofmendes, Я написал производный класс TextView, который автоматически изменяет размер текста и поддерживает несколько строк.

import android.content.Context;
import android.os.Handler;
import android.text.Layout;
import android.text.TextPaint;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.TypedValue;

public class AutoResizeTextView extends TextView {

    private Handler measureHandler = new Handler();
    private Runnable requestLayout = new Runnable() {
        @Override
        public void run() {
            requestLayout();
        }
    };

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

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

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

    @Override
    protected void onMeasure(final int widthMeasureSpec,
                             final int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        final float maxWidth = getWidth();
        final float maxHeight = getHeight();
        if (maxWidth < 1.0f || maxHeight < 1.0f) {
            return;
        }

        int index = 0;
        int lineCount = 0;
        CharSequence text = getText();
        final TextPaint paint = getPaint();
        while (index < text.length()) {
            index += paint.breakText(text, index, text.length(), true, maxWidth, null);
            lineCount++;
        }
        final float height = lineCount * getLineHeight() + (lineCount > 0 ?
                (lineCount - 1) * paint.getFontSpacing() : 0);
        if (height > maxHeight) {
            final float textSize = getTextSize();
            setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
            measureHandler.post(requestLayout);
        }
    }
}

компилируемый, фиксированный код: скопируйте-вставьте этот, а не принятый, потому что вам нужно его исправить:)

public static int getHeightOfMultiLineText(String text, int textSize, int maxWidth) {
    TextPaint paint = new TextPaint();
    paint.setTextSize(textSize);
    int index = 0;
    int lineCount = 0;
    while (index < text.length()) {
        index += paint.breakText(text, index, text.length(), true, maxWidth, null);
        lineCount++;
    }

    Rect bounds = new Rect();
    paint.getTextBounds("Yy", 0, 2, bounds);
    // obtain space between lines
    double lineSpacing = Math.max(0, ((lineCount - 1) * bounds.height() * 0.25));

    return (int) Math.floor(lineSpacing + lineCount * bounds.height());
}

нужно использовать, спасибо.

важно: вам нужно учитывать масштабный фактор

    int textSize = 50;
    int maxHeight = boundsTv.height();
    while (getHeightOfMultiLineText(text, textSize, params.width) > maxHeight) {
        textSize--;
    }
    float scaleFactor =  mainActivity.getResources().getDisplayMetrics().scaledDensity;     
    float temp = 1.2f;

    tvText.setTextSize((float) (textSize / (temp*scaleFactor)));