Как настроить размер шрифта текста, чтобы он соответствовал текстовому просмотру

Есть ли способ в Android настроить размер текста в текстовом представлении, чтобы он соответствовал занимаемому пространству?

Например. Я использую TableLayout и добавляю по несколько TextView в каждую строку. Поскольку я не хочу, чтобы TextViews обертывали текст, я предпочитаю видеть, что это уменьшает размер шрифта содержимого.

Любые идеи?

Я пробовал measureText, но, поскольку я не знаю размера столбца, его использование затруднительно. Это код, в котором я хочу изменить размер шрифта на то, что подходит

TableRow row = new TableRow(this);   
for (int i=0; i < ColumnNames.length; i++) {    
    TextView textColumn = new TextView(this);      
    textColumn.setText(ColumnNames[i]);
    textColumn.setPadding(0, 0, 1, 0);
    textColumn.setTextColor(getResources().getColor(R.drawable.text_default));          
    row.addView(textColumn, new TableRow.LayoutParams()); 
} 
table.addView(row, new TableLayout.LayoutParams());  

person rudas    schedule 11.04.2010    source источник
comment
Проверьте мое решение на основе кода Данни здесь stackoverflow.com/questions/5033012/ Примечание. Я не реализовал его с помощью цикла. PS: спасибо dunni   -  person vsm    schedule 12.03.2011


Ответы (23)


Приведенное ниже решение включает в себя все приведенные здесь предложения. Он начинается с того, что изначально было опубликовано Данни. Он использует двоичный поиск, такой как gjpc, но он немного более читабелен. Он также включает исправления ошибок gregm и мои собственные исправления.

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

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

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

    private void initialise() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) 
    { 
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            mTestPaint.setTextSize(size);
            if(mTestPaint.measureText(text) >= targetWidth) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
        }
    }

    //Attributes
    private Paint mTestPaint;
}
person speedplane    schedule 24.10.2011
comment
Спасибо за объединение всех отзывов. Я вижу, что решение учитывает только ширину. Моя проблема в том, что фонд превышает высоту. - person AlikElzin-kilaka; 19.12.2011
comment
О, вроде бы частично работает во время выполнения. На эмуляторе устройства текст обрезан наполовину. В редакторе макетов Eclipse все выглядит нормально. Любые идеи? - person AlikElzin-kilaka; 19.12.2011
comment
У меня отлично работает. Спасибо за публикацию. Как мне ввести атрибут XML, чтобы указать hi / lo - и использовать значение атрибута (если оно существует - сохраняя существующие значения по умолчанию) в методе refitText? - person Ed Sinek; 25.06.2012
comment
похоже, этот пост поможет (добавление пользовательских атрибутов xml для hi / lo): stackoverflow.com/a/8090772/156611 < / а> - person Ed Sinek; 25.06.2012
comment
@speedplane Есть ли способ также центрировать этот текст? - person RoflcoptrException; 29.06.2012
comment
@kilaka, кода, который вы установили на веб-сайте ppl, не существует. пожалуйста, разместите это здесь ... - person android developer; 10.09.2012
comment
@kilaka Это все еще не работает, потому что недостаточно хорошо обрабатывает высоту текста. Кроме того, я думаю, что вместо двоичного поиска вы можете просто использовать trySize * = availableWidth / measureTextWidth; то же самое по высоте. - person android developer; 14.04.2013
comment
Это было отличное решение! Если кто-то еще новичок в разработке Android и не совсем знает, как реализовать расширенное представление в XML, это выглядит так: <com.example.zengame1.FontFitTextView android:paddingTop="5dip" android:id="@+id/childs_name" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:layout_gravity="center" android:textSize="@dimen/text_size"/> - person Casey Murray; 28.08.2013
comment
Хороший ответ (но недостаточно хорош, чтобы быть выбранным?). Также следует реализовать третий конструктор для TextView, как показано здесь: file: ///Users/andrew/android-sdk-mac_x86/docs/reference/android/widget/TextView.html - person Andrew Mackenzie; 20.11.2013
comment
Хорошее решение. Если вам нужен действительно большой размер текста для очень больших экранов, увеличьте начальное значение float hi. - person Ricardo; 19.01.2014
comment
Любой способ изменить размер только в том случае, если текст превышает ширину родительского элемента? Например, я не хочу увеличивать размер текста, если он подходит. - person StuStirling; 13.03.2014
comment
Не работает в моем android 4.4. Размер текста не работает. - person Rahul Rastogi; 14.08.2014
comment
Отличная работа! Получил работу также с кнопками. Чтобы позаботиться о высоте текстового поля, просто установите float hi = this.getHeight() - this.getPaddingBottom() - this.getPaddingTop(); - person AlexGuti; 09.09.2014
comment
Я не могу поверить, что у этого есть столько голосов, когда это явно не работает. Он полностью игнорирует высоту текста, поэтому в случаях, когда текст состоит всего из пары символов, текст может быть разрезан пополам, потому что он слишком высок для текстового представления. - person user3690202; 12.12.2016
comment
@ user3690202, теперь я использую библиотеку github.com/grantland/android-autofittextview для достижения такого поведения . Удачи! - person AlexGuti; 12.12.2016
comment
Я знаю, что это было давно, и, вероятно, никто не будет это читать, но: зачем вам выполнять двоичный поиск, если вы можете очень точно оценить значение? вы можете очень точно оценить оптимальный размер на mTestPaint.set(this.getPaint()); float v = mTestPaint.measureText(text); if (v > targetWidth) hi = hi * targetWidth / v; - person Rafael Lima; 19.07.2018

Я написал класс, который расширяет TextView и делает это. Он просто использует measureText, как вы предлагаете. В основном он имеет максимальный размер текста и минимальный размер текста (который можно изменить), и он просто просматривает размеры между ними с шагом 1, пока не найдет самый большой, который подходит. Не особо изящно, но другого способа не знаю.

Вот код:

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

public class FontFitTextView extends TextView {

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

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

    private void initialise() {
        testPaint = new Paint();
        testPaint.set(this.getPaint());
        //max size defaults to the intially specified text size unless it is too small
        maxTextSize = this.getTextSize();
        if (maxTextSize < 11) {
            maxTextSize = 20;
        }
        minTextSize = 10;
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) { 
        if (textWidth > 0) {
            int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
            float trySize = maxTextSize;

            testPaint.setTextSize(trySize);
            while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) {
                trySize -= 1;
                if (trySize <= minTextSize) {
                    trySize = minTextSize;
                    break;
                }
                testPaint.setTextSize(trySize);
            }

            this.setTextSize(trySize);
        }
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
        }
    }

    //Getters and Setters
    public float getMinTextSize() {
        return minTextSize;
    }

    public void setMinTextSize(int minTextSize) {
        this.minTextSize = minTextSize;
    }

    public float getMaxTextSize() {
        return maxTextSize;
    }

    public void setMaxTextSize(int minTextSize) {
        this.maxTextSize = minTextSize;
    }

    //Attributes
    private Paint testPaint;
    private float minTextSize;
    private float maxTextSize;

}
person dunni    schedule 31.07.2010
comment
Бинарный поиск обычно быстрее, чем линейный поиск. - person Ted Hopp; 28.01.2011
comment
я думаю, что было бы разумнее сделать refitText в onLayout, а не onSizeChanged - person LiangWang; 04.01.2016
comment
Не могли бы вы взглянуть на мою проблему stackoverflow.com/questions/36265448/ - person Muhammad; 29.03.2016

Это FontFitTextView скоростного самолета, но он только уменьшает размер шрифта, если необходимо, чтобы текст поместился, и сохраняет размер шрифта в противном случае. Он не увеличивает размер шрифта до его высоты.

public class FontFitTextView extends TextView {

    // Attributes
    private Paint mTestPaint;
    private float defaultTextSize;

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

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

    private void initialize() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        defaultTextSize = getTextSize();
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) {

        if (textWidth <= 0 || text.isEmpty())
            return;

        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();

        // this is most likely a non-relevant call
        if( targetWidth<=2 )
            return;

        // text already fits with the xml-defined font size?
        mTestPaint.set(this.getPaint());
        mTestPaint.setTextSize(defaultTextSize);
        if(mTestPaint.measureText(text) <= targetWidth) {
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
            return;
        }

        // adjust text size using binary search for efficiency
        float hi = defaultTextSize;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be
        while (hi - lo > threshold) {
            float size = (hi + lo) / 2;
            mTestPaint.setTextSize(size);
            if(mTestPaint.measureText(text) >= targetWidth ) 
                hi = size; // too big
            else 
                lo = size; // too small

        }

        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            refitText(this.getText().toString(), w);
        }
    }

}

Вот пример того, как его можно использовать в xml:

<com.your.package.activity.widget.FontFitTextView
    android:id="@+id/my_id"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:text="My Text"
    android:textSize="60sp" />

Это позволит сохранить размер шрифта 60sp до тех пор, пока текст соответствует ширине. Если текст длиннее, размер шрифта уменьшится. В этом случае высота TextViews также изменится из-за height=wrap_content.

Если вы обнаружите какие-либо ошибки, не стесняйтесь редактировать.

person sulai    schedule 24.10.2012
comment
Как вы это использовали? Я добавил к своему ответу пример того, как я его использую. Я тестировал его с различными версиями Android, эмуляторами и редактором графического макета ADT. - person sulai; 23.11.2012
comment
По сути, я не указывал конкретную высоту. Ваш onMeasure позволяет представлению взять верх, если оно того хочет. Мне удалось придумать решение, изменив последнюю строку в подпрограмме на this.setMeasuredDimension(parentWidth, height); - person PearsonArtPhoto; 23.11.2012
comment
Отличный ввод, поправил код :) Теперь работает с match_parent и wrap_content. - person sulai; 26.11.2012
comment
Почему вы проверяете запись пустой строки .equals (s) вместо простого s.isEmpty () ?? Или s.length () == 0? Не понимаю, почему я иногда вижу эти тесты на равенство. - person Zordid; 12.06.2013
comment
Хороший замечание @Zordid, просто не знал о string.isEmpty() до сих пор :) - person sulai; 12.06.2013
comment
text.isEmpty() Для вызова требуется уровень API 9 (текущий мин. 8): java.lang.String # isEmpty измените его на text.equals(null) - person Pratik Butani; 18.01.2014
comment
@PratikButani, спасибо, что указали на это. эквивалент text.isEmpty() будет "".equals(text). - person sulai; 18.01.2014
comment
в реальном мире, хотите верьте, хотите нет, но при срабатывании изменения размера onMeasure будет вызываться много раз (›20). Думаю, достаточно поместить refitText в onSizeChanged и onTextChanged. (Я тестировал его на EditText) - person LiangWang; 04.01.2016
comment
Функция гравитации не работает, например: android: gravity = center_vertical | center_horizontal Как я могу этого добиться? - person chari sharma; 01.08.2018
comment
весь текст поддерживает только одну строку, есть возможность добавить несколько строк. - person chari sharma; 01.08.2018

Вот мое решение, которое работает на эмуляторе и телефонах, но не очень хорошо в редакторе макетов Eclipse. Он вдохновлен кодом kilaka, но размер текста получается не из Paint, а путем измерения самого TextView, вызывающего measure(0, 0).

Класс Java:

public class FontFitTextView extends TextView
{
    private static final float THRESHOLD = 0.5f;

    private enum Mode { Width, Height, Both, None }

    private int minTextSize = 1;
    private int maxTextSize = 1000;

    private Mode mode = Mode.None;
    private boolean inComputation;
    private int widthMeasureSpec;
    private int heightMeasureSpec;

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

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

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

            TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
            maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
            minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
            tAttrs.recycle();
    }

    private void resizeText() {
            if (getWidth() <= 0 || getHeight() <= 0)
                    return;
            if(mode == Mode.None)
                    return;

            final int targetWidth = getWidth();
            final int targetHeight = getHeight();

            inComputation = true;
            float higherSize = maxTextSize;
            float lowerSize = minTextSize;
            float textSize = getTextSize();
            while(higherSize - lowerSize > THRESHOLD) {
                    textSize = (higherSize + lowerSize) / 2;
                    if (isTooBig(textSize, targetWidth, targetHeight)) {
                            higherSize = textSize; 
                    } else {
                            lowerSize = textSize;
                    }
            }
            setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
            measure(widthMeasureSpec, heightMeasureSpec);
            inComputation = false;
    }

    private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            measure(0, 0);
            if(mode == Mode.Both)
                    return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
            if(mode == Mode.Width)
                    return getMeasuredWidth() >= targetWidth;
            else
                    return getMeasuredHeight() >= targetHeight;
    }

    private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
                    return Mode.Both;
            if(widthMode == MeasureSpec.EXACTLY)
                    return Mode.Width;
            if(heightMode == MeasureSpec.EXACTLY)
                    return Mode.Height;
            return Mode.None;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if(!inComputation) {
                    this.widthMeasureSpec = widthMeasureSpec;
                    this.heightMeasureSpec = heightMeasureSpec;
                    mode = getMode(widthMeasureSpec, heightMeasureSpec);
                    resizeText();
            }
    }

    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
            resizeText();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (w != oldw || h != oldh)
                    resizeText();
    }

    public int getMinTextSize() {
            return minTextSize;
    }

    public void setMinTextSize(int minTextSize) {
            this.minTextSize = minTextSize;
            resizeText();
    }

    public int getMaxTextSize() {
            return maxTextSize;
    }

    public void setMaxTextSize(int maxTextSize) {
            this.maxTextSize = maxTextSize;
            resizeText();
    }
}

Файл атрибутов XML:

<resources>
    <declare-styleable name="FontFitTextView">
        <attr name="minTextSize" format="dimension" />
        <attr name="maxTextSize" format="dimension" />
    </declare-styleable>
</resources>

Последнюю версию этого класса можно найти в моем github. Надеюсь, это может быть кому-то полезно. Если обнаружена ошибка или код требует объяснения, не стесняйтесь открывать вопрос на Github.

person yDelouis    schedule 04.11.2012
comment
Он отлично работает на моем Galaxy Nexus, но у меня проблемы с устройствами с маленькими экранами. - person Tony Ceralva; 18.12.2012
comment
В чем проблема с устройствами с маленькими экранами? - person yDelouis; 18.12.2012
comment
К сожалению, проблема не в маленьких экранах, ваше представление работает не на всех устройствах с Android 4.0, включая эмулятор. Я открыл новую проблему в вашем GitHub - person Tony Ceralva; 19.12.2012

Большое спасибо https://stackoverflow.com/users/234270/speedplane. Отличный ответ!

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

package com.<your_package>;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;


public class FontFitTextView extends TextView
{

    private Paint mTestPaint;
    private float maxFontSize;
    private static final float MAX_FONT_SIZE_DEFAULT_VALUE = 20f;

    public FontFitTextView(Context context)
    {
        super(context);
        initialise(context, null);
    }

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

    public FontFitTextView(Context context, AttributeSet attributeSet, int defStyle)
    {
        super(context, attributeSet, defStyle);
        initialise(context, attributeSet);
    }

    private void initialise(Context context, AttributeSet attributeSet)
    {
        if(attributeSet!=null)
        {
            TypedArray styledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.FontFitTextView);
            maxFontSize = styledAttributes.getDimension(R.styleable.FontFitTextView_maxFontSize, MAX_FONT_SIZE_DEFAULT_VALUE);
            styledAttributes.recycle();
        }
        else
        {
            maxFontSize = MAX_FONT_SIZE_DEFAULT_VALUE;
        }

        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth, int textHeight)
    {
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
        float hi = maxFontSize;
        float lo = 2;
//      final float threshold = 0.5f; // How close we have to be
        final float threshold = 1f; // How close we have to be

        mTestPaint.set(this.getPaint());

        Rect bounds = new Rect();

        while ((hi - lo) > threshold)
        {
            float size = (hi + lo) / 2;
            mTestPaint.setTextSize(size);

            mTestPaint.getTextBounds(text, 0, text.length(), bounds);

            if (bounds.width() >= targetWidth || bounds.height() >= targetHeight)
                hi = size; // too big
            else
                lo = size; // too small

//          if (mTestPaint.measureText(text) >= targetWidth)
//              hi = size; // too big
//          else
//              lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth, height);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after)
    {
        refitText(text.toString(), this.getWidth(), this.getHeight());
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        if (w != oldw)
        {
            refitText(this.getText().toString(), w, h);
        }
    }
}

Соответствующий файл /res/values/attr.xml:

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

    <declare-styleable name="FontFitTextView">
        <attr name="maxFontSize" format="dimension" />
    </declare-styleable>

</resources>

Пример:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:res-auto="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home_Layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background"
    tools:ignore="ContentDescription" >
...

 <com.<your_package>.FontFitTextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:singleLine="true"
                    android:text="Sample Text"
                    android:textSize="28sp"
                    res-auto:maxFontSize="35sp"/>

...
</RelativeLayout>

Чтобы использовать новый атрибут maxFontSize, не забудьте добавить xmlns:res-auto="http://schemas.android.com/apk/res-auto", как показано в примере.

person Pascal    schedule 10.03.2013
comment
Извините, но он работает не во всех случаях. Я также думаю, что он неправильно обрабатывает несколько строк. - person android developer; 14.04.2013
comment
Ох .. Не могли бы вы дать мне чехол, для которого он не работает? (Многострочная не поддерживается, вы правы) - person Pascal; 15.04.2013
comment
Много случаев. Я создал случайный тестер, чтобы доказать, что это правильно. Вот один пример: ширина представления: 317 пикселей, высота: 137 пикселей, текст: q7Lr. Я вижу следующее: tinypic.com/view.php?pic=2dv5yf9&s=6 < / а>. вот пример моего проекта: mega.co.nz/. Я думаю, что такое представление должно обрабатывать многострочные, поддерживать любой размер шрифтов, обрабатывать высоту, а не только ширину ... К сожалению, ни один из примеров, которые я нашел, не работал хорошо. - person android developer; 15.04.2013
comment
хорошо, определенно не полное решение. извиняюсь. Пока нет времени улучшать его .. Если вы можете улучшить его, не стесняйтесь размещать свое решение. - person Pascal; 15.04.2013
comment
Я создал новый поток, в котором показаны мои тесты, надеясь, что кто-то сможет найти хорошее решение: stackoverflow.com/questions/16017165/ - person android developer; 15.04.2013
comment
Я пытаюсь реализовать это в своем виджете, но все еще не получаю виджет? Я разместил здесь справку ссылка Я знаю, что проблема здесь в view.setTextViewText() - person Dino; 24.04.2015

Теперь вы можете сделать это без сторонней библиотеки или виджета. Он встроен в TextView на уровне API 26. Добавьте android:autoSizeTextType="uniform" в свой TextView и установите для него высоту. Это все. Используйте app:autoSizeTextType="uniform" для обратной совместимости

https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html.

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform" />

Вы также можете использовать TextViewCompat для совместимости.

person arsent    schedule 17.10.2017
comment
Используйте app: autoSizeTextType = uniform для обратной совместимости, потому что android: autoSizeTextType = uniform работает только на уровне API 26 и выше. - person Atul Bhardwaj; 23.07.2020

У меня была такая же проблема, и я написал класс, который, кажется, мне подходит. По сути, я использовал статический макет, чтобы нарисовать текст на отдельном холсте и проводить повторные измерения, пока не найду подходящий размер шрифта. Вы можете увидеть опубликованный курс в теме ниже. Я надеюсь, что это помогает.

Автоматическое масштабирование текста TextView для соответствия границам

person Chase    schedule 04.04.2011

Небольшая модификация onMeasure:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    refitText(this.getText().toString(), parentWidth);
    this.setMeasuredDimension(parentWidth, parentHeight);
}

И бинарный поиск по refitText:

private void refitText(String text, int textWidth) 
{ 
    if (textWidth > 0) 
    {
        int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();         
        int trySize = (int)maxTextSize;
        int increment = ~( trySize - (int)minTextSize ) / 2;

        testPaint.setTextSize(trySize);
        while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) 
        {
            trySize += increment;
            increment = ( increment == 0 ) ? -1 : ~increment / 2;
            if (trySize <= minTextSize) 
            {
                trySize = (int)minTextSize;
                break;
            }
            testPaint.setTextSize(trySize);
        }

        this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
    }
}
person gjpc    schedule 15.02.2011
comment
Вместо двоичного поиска неплохо работает простое масштабирование: trySize *= availableWidth / measured_width (затем фиксируется до minTextSize). - person Ted Hopp; 02.03.2011

Я обнаружил, что следующие мне хорошо подходят. Он не зацикливается и учитывает как высоту, так и ширину. Обратите внимание, что при вызове setTextSize в представлении важно указать модуль PX. Спасибо совету из предыдущего поста за это!

Paint paint = adjustTextSize(getPaint(), numChars, maxWidth, maxHeight);
setTextSize(TypedValue.COMPLEX_UNIT_PX,paint.getTextSize());

Вот процедура, которую я использую, передавая getPaint () из представления. Строка из 10 символов с «широким» символом используется для оценки ширины независимо от фактической строки.

private static final String text10="OOOOOOOOOO";
public static Paint adjustTextSize(Paint paint, int numCharacters, int widthPixels, int heightPixels) {
    float width = paint.measureText(text10)*numCharacters/text10.length();
    float newSize = (int)((widthPixels/width)*paint.getTextSize());
    paint.setTextSize(newSize);

    // remeasure with font size near our desired result
    width = paint.measureText(text10)*numCharacters/text10.length();
    newSize = (int)((widthPixels/width)*paint.getTextSize());
    paint.setTextSize(newSize);

    // Check height constraints
    FontMetricsInt metrics = paint.getFontMetricsInt();
    float textHeight = metrics.descent-metrics.ascent;
    if (textHeight > heightPixels) {
        newSize = (int)(newSize * (heightPixels/textHeight));
        paint.setTextSize(newSize);
    }

    return paint;
}
person Glenn    schedule 02.09.2011
comment
Как включить его в XML-макет? - person AlikElzin-kilaka; 19.12.2011
comment
Этот код полезен для размещения текста в существующем представлении в области ограниченного размера, или вы можете создать свой собственный производный класс из TextView и переопределить onMeasure, как показано в других сообщениях. Сам по себе его нельзя использовать в макете. - person Glenn; 04.02.2012
comment
Убедитесь, что вы проверили размеры вида после его рисования. Делать это во время onCreate () слишком рано. Я использовал ViewTreeObserver, чтобы убедиться, что измерения были сделаны в нужное время. - person SMBiggs; 27.08.2015

Используйте app:autoSizeTextType="uniform" для обратной совместимости, потому что android:autoSizeTextType="uniform" работает только с уровнем API 26 и выше.

person Suraj Vaishnav    schedule 27.08.2018
comment
Спасибо, Сурадж. Это лучшее решение - person Atul Bhardwaj; 23.07.2020
comment
Я проверил его на SDK 24, и он не работает. Вы уверены, что app:.. будет работать с SDK low 26? - person Georgiy Chebotarev; 04.09.2020
comment
Да, это работает как пространство имен приложения, обеспечивающее обратную совместимость. Об этом есть подробная статья, проверьте, возможно, это решит вашу проблему. medium.com/over- инженерия / - person Suraj Vaishnav; 04.09.2020

Работает с модификацией

Вам нужно установить размер текстового представления следующим образом, потому что в противном случае setTextSize предполагает, что значение находится в единицах SP:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

И вам нужно было явно добавить этот код.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    refitText(this.getText().toString(), parentWidth);
}
person gregm    schedule 10.12.2010

У меня была эта боль в моих проектах так долго, пока я не нашел эту библиотеку:

compile 'me.grantland:autofittextview:0.2.+'

Вам просто нужно добавить xml в соответствии с вашими потребностями, и все готово. Например:

<me.grantland.widget.AutofitTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:maxLines="2"
android:textSize="40sp"
autofit:minTextSize="16sp"
/>
person Giedrius Šlikas    schedule 03.07.2017

Я использовал вариант решения Dunni, описанный выше, но этот конкретный код у меня не работал. В частности, при попытке использовать объект Paint, для которого заданы черты объекта Paint представления, а затем при вызове measureText (), он не возвращает то же значение, что и прямой вызов объекта Paint представления. Возможно, есть некоторые различия в том, как настроены мои взгляды, которые делают поведение другим.

Мое решение заключалось в том, чтобы напрямую использовать Paint представления, даже несмотря на то, что при многократном изменении размера шрифта для представления могли быть некоторые потери производительности.

person ThomasW    schedule 19.11.2010

Я работал над улучшением отличного решения для Speedplane и придумал это. Он управляет высотой, включая настройку поля, чтобы текст был правильно центрирован по вертикали.

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

import android.content.Context;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

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

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

    private void initialize() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());

        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth,int textHeight) 
    { 
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
        float hi = Math.min(targetHeight,100);
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        Rect bounds = new Rect();

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            mTestPaint.setTextSize(size);
            mTestPaint.getTextBounds(text, 0, text.length(), bounds);
            if((mTestPaint.measureText(text)) >= targetWidth || (1+(2*(size+(float)bounds.top)-bounds.bottom)) >=targetHeight) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX,(float) lo);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth,height);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth(),this.getHeight());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {

        if (w != oldw) {
            refitText(this.getText().toString(), w,h);
        }
    }

    //Attributes
    private Paint mTestPaint;
}
person PearsonArtPhoto    schedule 23.11.2012

Google уже сделал эту функцию.

<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform" />

https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html.

person tse    schedule 12.11.2017

Вдохновленный предыдущими постерами, я хотел поделиться своим решением. Он работает с коэффициентом масштабирования, который применяется к предыдущему размеру шрифта, чтобы он соответствовал доступному пространству. Помимо предотвращения неожиданного поведения метода TextViews onDraw, он просто рисует текст самостоятельно.

public class FontFitTextView extends TextView {

    // How much of the available space should be used in percent.
    private static final float MARGINHEIGHT = 0.8f;
    private static final float MARGINWIDTH = 0.8f;

    private Paint paint;
    private int viewWidth;
    private int viewHeight;
    private float textHeight;
    private float textWidth;

    public FontFitTextView(Context c) {
        this(c, null);
    }

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

    // Default constructor override
    public FontFitTextView(Context c, AttributeSet attrs, int defStyle) {
        super(c, attrs, defStyle);
        initComponent();
    }

    private void initComponent() {
        paint = new Paint();
        paint.setTextSize(30);
        paint.setTextAlign(Align.CENTER);
        paint.setAntiAlias(true);
    }

    public void setFontColor(int c) {
        paint.setColor(c);
    }

    private void calcTextSize(String s, Canvas c) {

        float availableHeight = viewHeight;
        float availableWidth = viewWidth;

        // This value scales the old font up or down to match the available
        // space.
        float scale = 1.0f;

        // Rectangle for measuring the text dimensions
        Rect rect = new Rect();
        float oldFontSize = paint.getTextSize();

        // Calculate the space used with old font size
        paint.getTextBounds(s, 0, s.length(), rect);
        textWidth = rect.width();
        textHeight = rect.height();

        // find scale-value to fit the text horizontally
        float scaleWidth = 1f;
        if (textWidth > 0.0f) {
            scaleWidth = (availableWidth) / textWidth * MARGINWIDTH;
        }

        // find scale-value to fit the text vertically
        float scaleHeight = 1f;
        if (textHeight > 0.0f) {
            scaleHeight = (availableHeight) / textHeight * MARGINHEIGHT;
        }

        // We are always limited by the smaller one
        if (scaleWidth < scaleHeight) {
            scale = scaleWidth;
        } else {
            scale = scaleHeight;
        }

        // We apply the scale to the old font size to make it bigger or smaller
        float newFontSize = (oldFontSize * scale);
        paint.setTextSize(newFontSize);
    }

    /**
     * Calculates the origin on the Y-Axis (width) for the text in this view.
     * 
     * @return
     */
    private float calcStartDrawingPosX() {
        float left = getMeasuredWidth();
        float centerY = left - (viewWidth / 2);
        return centerY;
    }

    /**
     * Calculates the origin on the Y-Axis (height) for the text in this view.
     * 
     * @return
     */
    private float calcStartDrawingPosY() {
        float bottom = getMeasuredHeight();
        // The paint only centers horizontally, origin on the Y-Axis stays at
        // the bottom, thus we have to lift the origin additionally by the
        // height of the font.
        float centerX = bottom - (viewHeight / 2) + (textHeight / 2);
        return centerX;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        String text = getText().toString();
        if (text.length() > 0) {
            calcTextSize(text, canvas);
            canvas.drawText(text, calcStartDrawingPosX(),
                    calcStartDrawingPosY(), paint);
        }
    };

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        viewWidth = w;
        viewHeight = h;
        super.onSizeChanged(w, h, oldw, oldh);
    }
}
person unSinn    schedule 27.08.2012

Это должно быть простое решение:

public void correctWidth(TextView textView, int desiredWidth)
{
    Paint paint = new Paint();
    Rect bounds = new Rect();

    paint.setTypeface(textView.getTypeface());
    float textSize = textView.getTextSize();
    paint.setTextSize(textSize);
    String text = textView.getText().toString();
    paint.getTextBounds(text, 0, text.length(), bounds);

    while (bounds.width() > desiredWidth)
    {
        textSize--;
        paint.setTextSize(textSize);
        paint.getTextBounds(text, 0, text.length(), bounds);
    }

    textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
person Hamzeh Soboh    schedule 23.09.2013

Расширьте TextView и переопределите onDraw с помощью приведенного ниже кода. Он сохранит соотношение сторон текста, но изменит его размер, чтобы заполнить пространство. При необходимости вы можете легко изменить код, чтобы растянуть его.

  @Override
  protected void onDraw(@NonNull Canvas canvas) {
    TextPaint textPaint = getPaint();
    textPaint.setColor(getCurrentTextColor());
    textPaint.setTextAlign(Paint.Align.CENTER);
    textPaint.drawableState = getDrawableState();

    String text = getText().toString();
    float desiredWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2;
    float desiredHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2;
    float textSize = textPaint.getTextSize();

    for (int i = 0; i < 10; i++) {
      textPaint.getTextBounds(text, 0, text.length(), rect);
      float width = rect.width();
      float height = rect.height();

      float deltaWidth = width - desiredWidth;
      float deltaHeight = height - desiredHeight;

      boolean fitsWidth = deltaWidth <= 0;
      boolean fitsHeight = deltaHeight <= 0;

      if ((fitsWidth && Math.abs(deltaHeight) < 1.0)
          || (fitsHeight && Math.abs(deltaWidth) < 1.0)) {
        // close enough
        break;
      }

      float adjustX = desiredWidth / width;
      float adjustY = desiredHeight / height;

      textSize = textSize * (adjustY < adjustX ? adjustY : adjustX);

      // adjust text size
      textPaint.setTextSize(textSize);
    }
    float x = desiredWidth / 2f;
    float y = desiredHeight / 2f - rect.top - rect.height() / 2f;
    canvas.drawText(text, x, y, textPaint);
  }
person Greg Bacchus    schedule 08.12.2014

Я написал короткий вспомогательный класс, который заставляет текстовое представление умещаться в пределах определенной ширины и добавляет многоточие "..." в конце, если минимальный размер текста не может быть достигнут.

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

Требуются пиксели, поэтому, если вы используете значения из dimen, вы можете назвать это так:


float minTextSizePx = getResources().getDimensionPixelSize(R.dimen.min_text_size);
float maxTextWidthPx = getResources().getDimensionPixelSize(R.dimen.max_text_width);
WidgetUtils.fitText(textView, text, minTextSizePx, maxTextWidthPx);

Я использую этот класс:


public class WidgetUtils {

    public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
        textView.setEllipsize(null);
        int size = (int)textView.getTextSize();
        while (true) {
            Rect bounds = new Rect();
            Paint textPaint = textView.getPaint();
            textPaint.getTextBounds(text, 0, text.length(), bounds);
            if(bounds.width() < maxWidthPx){
                break;
            }
            if (size <= minTextSizePx) {
                textView.setEllipsize(TextUtils.TruncateAt.END);
                break;
            }
            size -= 1;
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
        }
    }
}
person Björn Kechel    schedule 24.07.2015
comment
Поддерживает ли это несколько строк текста? - person Roymunson; 18.02.2016

Если установлена ​​трансформация, такая как allCaps, сближение с самолетом будет ошибочным. Я исправил это, в результате получился следующий код (извините, моя репутация не позволяет мне добавить это в качестве комментария к решению Speedplane):

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

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

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

    private void initialise() {
        mTestPaint = new Paint();
        mTestPaint.set(this.getPaint());
        //max size defaults to the initially specified text size unless it is too small
    }

    /* Re size the font so the specified text fits in the text box
     * assuming the text box is the specified width.
     */
    private void refitText(String text, int textWidth) 
    { 
        if (getTransformationMethod() != null) {
            text = getTransformationMethod().getTransformation(text, this).toString();
        }

        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            if(mTestPaint.measureText(text) >= targetWidth) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int height = getMeasuredHeight();
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, height);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        refitText(text.toString(), this.getWidth());
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh) {
        if (w != oldw) {
            refitText(this.getText().toString(), w);
      }
    }

    //Attributes
    private Paint mTestPaint;
}
person dpoetzsch    schedule 12.09.2015

Я не знаю, что это правильный способ или нет, потому что он работает ... возьмите свое мнение и проверьте OnGlobalLayoutListener () и получите textview linecount, затем установите textSize.

 yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (textView.getLineCount()>=3) {
                textView.setTextSize(20);
            }else{
                //add somthing
              }
        }
    });

Это очень простой код из нескольких строк.

person Mathan Chinna    schedule 25.10.2016

В моем случае использование app:autoSize не решало всех случаев, например, не предотвращает разбиение слов

Это то, что я в конечном итоге использовал, он уменьшит размер текста, чтобы не было разрывов слов на нескольких строках

/**
 * Resizes down the text size so that there are no word breaks
 */
class AutoFitTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

    private val paint = Paint()
    private val bounds = Rect()

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        var shouldResize = false
        paint.typeface = typeface
        var textSize = textSize
        paint.textSize = textSize
        val biggestWord: String = text.split(" ").maxByOrNull { it.count() } ?: return

        // Set bounds equal to the biggest word bounds
        paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)

        // Iterate to reduce the text size so that it makes the biggest word fit the line
        while ((bounds.width() + paddingStart + paddingEnd + paint.fontSpacing) > measuredWidth) {
            textSize--
            paint.textSize = textSize
            paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)
            shouldResize = true
        }
        if (shouldResize) {
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
        }
    }
}
person Mohamed Khaled    schedule 13.01.2021

person    schedule
comment
Я не думаю, что это сработает, просто прочитав ваш код. Помните, что вопрос был об автоматической настройке размера шрифта текстового представления, чтобы он соответствовал представлению. Вы устанавливаете фиксированный размер шрифта. - person sulai; 15.03.2013