Подтвердить что ты не робот

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

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

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

В основном моя цель такова:

Phase 1Phase 2Phase 3Phase 4

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

Одна из моих идей, чтобы решить это, была примерно такая:

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 - это константа, определяющая максимальный размер шрифта (оформление текста в TextView)

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

Это вызывалось каждый раз, когда текст в текстовом виде был изменен.

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, нужно учитывать только высоту представления, так как при ее настройке андроид при необходимости автоматически создавал многострочный.

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

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

4b9b3361

Ответ 1

Пока я ждал возможного решения этого вопроса, я экспериментировал и пытался понять это.

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

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

public int breakText (текст CharSequence, int start, int end, boolean measureForwards, float maxWidth, float [] measuredWidth)

Добавлен в уровень API 1

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

Я совмещаю это с краской 'getTextBounds', которая:

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

Добавлен в уровень API 1

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

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

Используя некоторое время, вы можете продолжать перемещать удаляющие символы из строки, которую вы хотите измерить, и получить количество строк (используя время (index < string.length)) и умножить это на высоту, полученную в getTextBounds.

Кроме того, вам нужно будет добавить высоту переменной для каждой из двух строк, которые представляют пространство между строками (которое не учитывается в 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--;

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

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

Ответ 2

У вас есть довольно хорошее решение этой проблемы, но я взглянул на это с другой точки зрения. Я думаю, что логика проще и понятнее, по крайней мере, для новых разработчиков 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, Rect и математические функции.

Ответ 3

спасибо за ваше решение, вы супермен! Я реализовал ваш код в производном классе TextView. Код преобразуется в код С# для 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" />

Ответ 4

Используя идею от 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);
        }
    }
}

Ответ 5

введите описание изображения здесь введите описание изображения здесь введите описание изображения здесь

Немного улучшенный ответ phamducgiam. Он показывает текст с уже установленным размером, а не скретч после показа. Выглядит уродливо в редакторе из-за запуска 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;
    }
}

Ответ 6

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

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