Как я могу всегда вставлять текст в однострочный TextView? [Дубликат]

Глядя на другие ответы, кажется, что есть много вещей, которые могут вызвать эту ошибку. Вот еще один.

Если у вас

  • есть пользовательский вид
  • , добавлено свойство @IBInspectable
  • , а затем удалено его

Тогда вы также можете получить ошибку, аналогичную

Не удалось установить (xxx) пользовательское проверенное свойство в [Свой пользовательский вид] ...: этот класс не ключевое значение, кодирующее для ключа [xxx].

Решение состоит в том, чтобы удалить старое свойство.

Откройте инспектор удостоверений для своего класса, выберите имя свойства в разделе «Определенные пользователем атрибуты времени выполнения» и нажмите кнопку «минус» (-).

743
задан Nathan Fig 19 May 2015 в 03:03
поделиться

32 ответа

С Android 8.0 (уровень Api 26) это функция по умолчанию. https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html

59
ответ дан VolkanSahin45 21 August 2018 в 04:30
поделиться
  • 1
    Хороший ответ всегда будет объяснять, что было сделано и почему это было сделано таким образом, не только для OP, но и для будущих посетителей SO. Пожалуйста, добавьте некоторое описание, чтобы другие понимали иначе, используя раздел комментариев. Старайтесь избегать комментариев в ответ, если у вас нет конкретного ответа. Пожалуйста, используйте поле комментариев ниже, и как только у вас будет достаточно репутации, вы сможете прокомментировать любую запись. :) – Rucha Bhatt 22 August 2017 в 10:22
  • 2
    Я меняю принятый ответ на этот вопрос, поскольку решение для Android Android должно иметь более высокую видимость, теперь, когда оно существует. @ Ответ Chase правил как лучшее решение в течение многих лет и может по-прежнему лучше подходит для некоторых целей, таких как приложения ниже уровня API 26. – Nathan Fig 29 August 2017 в 20:12
  • 3
    Разработчики @NathanFig могут использовать эту функцию в библиотеках поддержки ниже уровня Api 26. – VolkanSahin45 30 August 2017 в 07:49
  • 4
    Полезно знать, но обратная совместимость всегда является фактором – Julius 20 October 2017 в 16:31
  • 5
    С android.support.v7.widget.AppCompatTextView вы можете использовать AutoSize прямо в Android 4.0 – grantnz 28 March 2018 в 02:48
7
ответ дан Think Twice Code Once 31 October 2018 в 23:51
поделиться

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

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

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */

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

/**
 * Text view that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if (mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }

    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {

        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        if (getTransformationMethod() != null) {
            text = getTransformationMethod().getTransformation(text, this);
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();
        // If there is a max text size set, use the lesser of that and the default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
        while (textHeight > height && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paint = new TextPaint(textPaint);
            // Draw using a static layout
            StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if (layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if (lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = textPaint.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while (width < lineWidth + ellipseWidth) {
                        lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paintCopy = new TextPaint(paint);
        // Update the text paint object
        paintCopy.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

Предупреждение. Существует важная фиксированная ошибка, влияющая на Android 3.1 - 4.04, в результате чего все виджеты AutoResizingTextView не работают. Пожалуйста, прочитайте: https://stackoverflow.com/a/21851157/2075875

1067
ответ дан 19 revs, 6 users 96% 21 August 2018 в 04:30
поделиться
  • 1
    Это действительно лучшее решение, которое я видел, очень хорошо сделано. Это не полностью соответствует моим потребностям из-за того, что Android любит сломать слова при упаковке, поэтому я буду придерживаться своего пользовательского решения, но ваш класс намного лучше подходит для общего использования. – Nathan Fig 5 April 2011 в 17:11
  • 2
    В духе Android, это все твое! И, надеюсь, когда вы создадите что-то, что может быть полезно, вы можете поделиться этим :) – Chase 13 December 2011 в 02:39
  • 3
    Итак, я сделал обновление для кода. Вычитание 1 из индекса строки было намеренным, и я добавил комментарий. Ошибка, которую вы получили, была, вероятно, из-за того, что не было достаточной высоты для рендеринга даже одной строки текста. Я добавил чек и очистил текст в этом случае. (Я работаю в Shibuya, поэтому я буду пить в любое время :) – Chase 6 January 2012 в 05:26
  • 4
    Вы пробовали последнюю отредактированную версию. Я сделал небольшую замену для Jelly Bean примерно неделю назад. Вероятно, это должно быть на github. – Chase 11 July 2012 в 06:43
  • 5
    не работает для TextView внутри элементов ListView, а также внутри. Включает другой макет – Vladyslav Matviienko 3 November 2013 в 18:44

Обходной путь для Android 4.x:

Я нашел AutoResizeTextView, и он отлично работает на моем эмуляторе Android 2.1. Я так это любил. Но, к сожалению, это не удалось на моем собственном 4.0.4 мобильном телефоне и 4.1 эмуляторе. После попытки найти это можно легко решить, добавив следующие атрибуты в класс AutoResizeTextView в xml:

android: ellipsize = "none"

android: singleLine = "true "

С двумя строками выше, теперь AutoResizeTextView отлично работает на моем 2.1 & amp; 4.1 и мой собственный сотовый телефон 4.0.4.

Надеюсь, это вам поможет. : -)

17
ответ дан agudoe2 21 August 2018 в 04:30
поделиться
  • 1
    Я определенно рекомендую больше решений, предложенных @onoelle. Он отлично работает во всех случаях. Например. в моем случае не было отдельного текстового представления. – Boris Strandjev 16 November 2012 в 12:51

Я начал с класса AutoResizeTextView Chase и внес незначительные изменения, чтобы он соответствовал как по вертикали, так и по горизонтали.

Я также обнаружил ошибку, которая вызывает исключение Null Pointer в редакторе макетов (в Eclipse) в некоторых довольно неясных условиях.

Изменить 1: Поместить текст как по вертикали, так и по горизонтали

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

Это изменение изменяет размер до тех пор, пока текст не поместится как по вертикали, так и по горизонтали.

В resizeText( int , int ) изменится с:

// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(textHeight > height && targetTextSize > mMinTextSize) {
    targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
    textHeight = getTextHeight(text, textPaint, width, targetTextSize);
    }

на:

// Get the required text height
int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
int textWidth  = getTextWidth(text, textPaint, width, targetTextSize);

// Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
while(((textHeight >= height) || (textWidth >= width) ) && targetTextSize > mMinTextSize) {
    targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
    textHeight = getTextHeight(text, textPaint, width, targetTextSize);
    textWidth  = getTextWidth(text, textPaint, width, targetTextSize);
    }

Затем, в конце файл, добавьте подпрограмму getTextWidth(); это просто слегка измененный getTextHeight(). Вероятно, было бы более эффективно комбинировать их с одной подпрограммой, которая возвращает как высоту, так и ширину.

// Set the text size of the text paint object and use a static layout to render text off screen before measuring
private int getTextWidth(CharSequence source, TextPaint paint, int width, float textSize) {
    // Update the text paint object
    paint.setTextSize(textSize);
    // Draw using a static layout
    StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
    layout.draw(sTextResizeCanvas);
    return layout.getWidth();
}  

Изменить 2: Исправить исключение EmptyStackException в редакторе макета Eclipse Android

неясные и очень точные условия, редактор макета не сможет отобразить графическое отображение макета; он будет генерировать исключение «EmptyStackException: null» в com.android.ide.eclipse.adt.

Необходимые условия: - создать виджет AutoResizeTextView - создать стиль для этого виджета - указать текстовый элемент в стиле; не в определении виджета

, как в:

res / layout / main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <com.ajw.DemoCrashInADT.AutoResizeTextView
        android:id="@+id/resizingText"
        style="@style/myTextStyle" />

</LinearLayout>

res / values ​​/ myStyles.xml:

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

    <style name="myTextStyle" parent="@android:style/Widget.TextView">
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_width">fill_parent</item>
        <item name="android:text">some message</item>
    </style>

</resources>

С этими файлами вы можете выбрать вкладку Графический макет при редактировании main.xml:

Ошибка

! EmptyStackException: null Сведения об исключении регистрируются в окне> Показать представление> Журнал ошибок

вместо графического представления макета.

Чтобы сохранить уже слишком длинная история короче, я проследил это до следующих строк (опять же в resizeText):

// If there is a max text size set, use the lesser of that and the default text size
float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

Проблема в том, что в определенных условиях mTextSize никогда не инициализируется; он имеет значение 0.

С приведенным выше, targetTextSize установлено на ноль (в результате Math.min).

Тот ноль передается на getTextHeight() ( и getTextWidth()) в качестве аргумента textSize. Когда он доходит до layout.draw(sTextResizeCanvas);, мы получаем исключение.

Более эффективно тестировать, если (mTextSize == 0) в начале resizeText() вместо тестирования в getTextHeight() и getTextWidth();

С этими обновлениями файл (как в моем тестовом приложении для демонстрации) теперь:

//
// from:  http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds
//
//

package com.ajw.DemoCrashInADT;

import android.content.Context;
import android.graphics.Canvas;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * Text view that auto adjusts text size to fit within the view. If the text
 * size equals the minimum text size and still does not fit, append with an
 * ellipsis.
 *
 * 2011-10-29 changes by Alan Jay Weiner
 *              * change to fit both vertically and horizontally  
 *              * test mTextSize for 0 in resizeText() to fix exception in Layout Editor
 *
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Off screen canvas for text size rendering
    private static final Canvas sTextResizeCanvas = new Canvas();

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for
    // resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;


    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }


    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }


    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }


    /**
     * When text changes, set the force resize flag to true and reset the text
     * size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }


    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }


    /**
     * Register listener to receive resize notifications
     *
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }


    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }


    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }


    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }


    /**
     * Set the upper text size limit and invalidate the view
     *
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }


    /**
     * Return upper text size limit
     *
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }


    /**
     * Set the lower text size limit and invalidate the view
     *
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }


    /**
     * Return lower text size limit
     *
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }


    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     *
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }


    /**
     * Return flag to add ellipsis to text that overflows at the smallest text
     * size
     *
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }


    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
        mMaxTextSize = mTextSize;
    }


    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom()
                    - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }


    /**
     * Resize the text size with specified width and height
     *
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no
        // text
        // or if mTextSize has not been initialized
        if (text == null || text.length() == 0 || height <= 0 || width <= 0
                || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // If there is a max text size set, use the lesser of that and the
        // default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize)
                : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        int textWidth = getTextWidth(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min
        // text size, incrementally try smaller sizes
        while (((textHeight > height) || (textWidth > width))
                && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            textWidth = getTextWidth(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append
        // an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            StaticLayout layout = new StaticLayout(text, textPaint, width,
                    Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            layout.draw(sTextResizeCanvas);
            int lastLine = layout.getLineForVertical(height) - 1;
            int start = layout.getLineStart(lastLine);
            int end = layout.getLineEnd(lastLine);
            float lineWidth = layout.getLineWidth(lastLine);
            float ellipseWidth = textPaint.measureText(mEllipsis);

            // Trim characters off until we have enough room to draw the
            // ellipsis
            while (width < lineWidth + ellipseWidth) {
                lineWidth = textPaint.measureText(text.subSequence(start, --end + 1)
                        .toString());
            }
            setText(text.subSequence(0, end) + mEllipsis);

        }

        // Some devices try to auto adjust line spacing, so force default line
        // spacing
        // and invalidate the layout as a side effect
        textPaint.setTextSize(targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }


    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint paint, int width,
            float textSize) {
        // Update the text paint object
        paint.setTextSize(textSize);
        // Draw using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        layout.draw(sTextResizeCanvas);
        return layout.getHeight();
    }


    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextWidth(CharSequence source, TextPaint paint, int width,
            float textSize) {
        // Update the text paint object
        paint.setTextSize(textSize);
        // Draw using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        layout.draw(sTextResizeCanvas);
        return layout.getWidth();
    }

}

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

22
ответ дан Alan Jay Weiner 21 August 2018 в 04:30
поделиться
  • 1
    ОШИБКА ОШИБКИ: getTextWidth() не работает вообще, поскольку вы передаете нужную ширину в конструкторе StaticLayout. Угадайте, какая ширина будет возвращена в этом случае из метода getWidth()? – se.solovyev 12 November 2011 в 14:12
  • 2
    Заметка для будущих людей, таких как я, которые наткнулись на это: объект Paint имеет метод measureText , который можно вызвать, чтобы получить ширину текста. – Xono 17 September 2012 в 05:26
  • 3
    Красивый. Этот код работал для меня на Android 4.1 на моем Galaxy Nexus в моем приложении KeepScore ( github.com/nolanlawson/KeepScore ), в то время как версия Chase этого не сделала. Запрос функции: пожалуйста, поместите этот код на GitHub, ребята! StackOverflow не является местом для исправлений и обзоров кода. :) – nlawson 28 December 2012 в 22:56
  • 4
    Как указано выше, layout.getWidth () просто возвращает ширину, с которой она была создана. Что я должен был сделать, чтобы сделать эту работу, так это создать макет шириной 4096, а затем вызвать getLineWidth () во всех строках и вернуть максимальное значение. – Edward Falk 14 January 2013 в 07:28
  • 5
    22 upvote для нерабочего кода / почему ?! Igave несколько строк и не соответствует пространству – user 2 November 2013 в 20:01

Вот такой подход. Это очень просто. Он использует последовательное приближение к нулю в fontsize и, как правило, может вычислять его менее чем за 10 итераций. Просто замените «activityWidth» на ширину любого вида, который вы используете для отображения текста. В моем примере он задан как личное поле ширины экрана. Исходный шрифт 198 устанавливается только в том случае, если метод генерирует исключение (чего на самом деле никогда не должно быть):

  private float GetFontSizeForScreenWidth(String text)
  {
    float fontsize = 198;

    try
    {
      Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
      paint.setColor(Color.RED);
      Typeface typeface = Typeface.create("Helvetica", Typeface.BOLD);
      paint.setTypeface(typeface);
      paint.setTextAlign(Align.CENTER);

      int lowVal = 0;
      int highVal = 2000;
      int currentVal = highVal;

      /*
       * Successively approximate the screen size until it is 
       * within 2 pixels of the maximum screen width. Generally
       * this will get you to the closest font size within about 10
       * iterations.
       */

      do
      {
        paint.setTextSize(currentVal);
        float textWidth = paint.measureText(text);

        float diff = activityWidth - textWidth;

        if ((diff >= 0) && (diff <= 2))
        {
          fontsize = paint.getTextSize();
          return fontsize;
        }

        if (textWidth > activityWidth)
          highVal = currentVal;
        else if (textWidth < activityWidth)
          lowVal = currentVal;
        else
        {
          fontsize = paint.getTextSize();
          return fontsize;
        }

        currentVal = (highVal - lowVal) / 2 + lowVal;

      } while (true);      
    }
    catch (Exception ex)
    {
      return fontsize;
    }
  }
0
ответ дан AndroidDev 21 August 2018 в 04:30
поделиться

Благодаря Chase и onoelle, для ленивых программистов, позвольте мне опубликовать здесь рабочую версию их фантастического объединенного кода, адаптированного к Button, вместо TextView.

Замените все ваши кнопки (не ImageButtons) с помощью кнопок AutoResizeTextButtons, и для них тоже исправлена ​​такая же проблема.

Вот код. Я просто удалил импорт.

/**
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT THE FUCK YOU WANT TO.
 *  made better by onoelle
 *  adapted for button by beppi
 */

/**
 * Text Button that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextButton extends Button {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(Button textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextButton(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if(mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if(changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();
        // If there is a max text size set, use the lesser of that and the default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
        while(textHeight > height && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paint = new TextPaint(textPaint);
            StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if(layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if(lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = textPaint.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while(width < lineWidth + ellipseWidth) {
                        lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
//      textPaint.setTextSize(targetTextSize);
     // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if(mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
          // modified: make a copy of the original TextPaint object for measuring
          // (apparently the object gets modified while measuring, see also the
          // docs for TextView.getPaint() (which states to access it read-only)
        // Update the text paint object
          TextPaint paint = new TextPaint(originalPaint);
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

Использование:

помещает AutoResizeTextButton внутри вашего xml вместо обычной кнопки, ничего не меняя. Внутри onCreate () put (например):

    myButton = (AutoResizeTextButton)getView().findViewById(id.myButton);
    myButton.setMinTextSize(8f);
    myButton.resizeText();
0
ответ дан Beppi's 21 August 2018 в 04:30
поделиться
  • 1
    Фон кнопки невидим. Вам нужно включить следующее в представление кнопки xml: style = & quot; android: attr / buttonBarButtonStyle & quot; – Kristy Welsh 9 December 2015 в 16:42

Вот перечисление того, что еще я нашел для любого, кто все еще ищет:

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

2) Вы можете попытаться взломать собственное решение, подобное этому , или классу dunni в this , что я и сделал с помощью getPaint () .measureText (str), чтобы найти нужный размер, но он стал намного более грязным, так как мне нужно его обернуть только в пробелы ...

3) Вы можете продолжать поиск - я попробовал больше альтернатив чем я могу рассчитывать. Совет Теда о StaticLayout не окупился за меня, но, может быть, там что-то есть; Я попытался использовать StaticLayout.getEllipsis (строка), чтобы определить, идет ли текст с экрана, и это не имеет никакого эффекта. См. Мой (в настоящее время не отвеченный) пост об этом здесь .

136
ответ дан Community 21 August 2018 в 04:30
поделиться
  • 1
    хорошо, я проверил вашу проблему. Вам не нужен этот AutoResizeTextView, проблема в другом. Я прокомментировал ваш вопрос. Эта проблема не имеет ничего общего с размером. – M-WaJeEh 29 August 2013 в 18:40
  • 2
    Я заметил, что есть @TargetApi (Build.VERSION_CODES.JELLY_BEAN). Может ли это работать на 2.3? – Nick Jian 30 September 2013 в 09:29
  • 3
    Рад, что вам понравилось, да, у меня есть лицензионная проверка. [Д0] docs.google.com/document/d/… – M-WaJeEh 13 February 2014 в 08:57
  • 4
    Похоже, что это не будет работать с пользовательскими Typefaces, так как вещи обрезаются вертикально. Как бы вы это сделали? Кто-нибудь пытался с пользовательскими шрифтами? – RealCasually 7 March 2014 в 23:00
  • 5
    @RobinHood Иногда StaticLayout не уважает width. Просто замените последние возвращаемые строки onTestSize() на это «if (availableSpace.contains (mTextRect)) {// может быть слишком маленьким, не волнуйтесь, мы найдем лучший ответ return -1; } else {if (mTextRect.bottom & lt; availableSpace.bottom & amp; mTextRect.right & gt; availableSpace.right) {// hack: O return -1; } // слишком большой возврат 1; }» – M-WaJeEh 24 March 2014 в 18:28

Я нашел следующее, чтобы работать хорошо для меня. Он не зацикливается и учитывает как высоту, так и ширину. Обратите внимание, что при вызове 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;
}
4
ответ дан Glenn 21 August 2018 в 04:30
поделиться

Мне нужно определенное решение. У меня есть текст и текст в моем макете. Текстовое изображение фиксируется по высоте и ширине. Когда пользователь начнет вводить текст edittext, текст должен сразу появиться в текстовом представлении. Текст в текстовом поле должен автоматически изменяться в соответствии с текстом. Поэтому я обновил решение Chase для работы для меня. Поэтому, когда текст изменяется в текстовом режиме, начинается изменение размера. Разница между моим и Soluton Chase: изменение размера выполняется, даже если пользователь УДАЛИТЬ некоторые символы. Я надеюсь, что это может кому-то помочь.

public class TextFitTextView extends TextView {

// Minimum text size for this text view
public static final float MIN_TEXT_SIZE = 10;

// Maximum text size for this text view - if it is 0, then the text acts
// like match_parent
public static final float MAX_TEXT_SIZE = 0;

// Our ellipse string
private static final String mEllipsis = "...";

// Text size that is set from code. This acts as a starting point for
// resizing
private float mTextSize;

// Lower bounds for text size
private float mMinTextSize = MIN_TEXT_SIZE;

// Max bounds for text size
private float mMaxTextSize = MAX_TEXT_SIZE;

// Text view line spacing multiplier
private float mSpacingMult = 1.0f;

// Text view additional line spacing
private float mSpacingAdd = 0.0f;

// Add ellipsis to text that overflows at the smallest text size
private boolean mAddEllipsis = true;

// Add ellipsis to text that overflows at the smallest text size
private int heightLimit;
private int widthLimit;

// Default constructor override
public TextFitTextView(Context context) {
    this(context, null);
}

// Default constructor when inflating from XML file
public TextFitTextView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

// Default constructor override
public TextFitTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    mTextSize = getTextSize();
}

/**
 * When text changes resize the text size.
 */
@Override
protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
    // if we are adding new chars to text
    if (before <= after && after != 1) {
        resizeText(true);
        // now we are deleting chars
    } else {
        resizeText(false);
    }
}

/**
 * Override the set text size to update our internal reference values
 */
@Override
public void setTextSize(float size) {
    super.setTextSize(size);
    mTextSize = getTextSize();
}

/**
 * Override the set text size to update our internal reference values
 */
@Override
public void setTextSize(int unit, float size) {
    super.setTextSize(unit, size);
    mTextSize = getTextSize();
}

/**
 * Override the set line spacing to update our internal reference values
 */
@Override
public void setLineSpacing(float add, float mult) {
    super.setLineSpacing(add, mult);
    mSpacingMult = mult;
    mSpacingAdd = add;
}

/**
 * Set the lower text size limit and invalidate the view
 * 
 * @param minTextSize
 */
public void setMinTextSize(float minTextSize) {
    mMinTextSize = minTextSize;
    requestLayout();
    invalidate();
}

/**
 * Return lower text size limit
 * 
 * @return
 */
public float getMinTextSize() {
    return mMinTextSize;
}

/**
 * Set flag to add ellipsis to text that overflows at the smallest text size
 * 
 * @param addEllipsis
 */
public void setAddEllipsis(boolean addEllipsis) {
    mAddEllipsis = addEllipsis;
}

/**
 * Return flag to add ellipsis to text that overflows at the smallest text
 * size
 * 
 * @return
 */
public boolean getAddEllipsis() {
    return mAddEllipsis;
}

/**
 * Get width and height limits
 */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    if (widthLimit == 0 && heightLimit == 0) {
        widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
        heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
    }
    super.onLayout(changed, left, top, right, bottom);
}

/**
 * Resize the text size with specified width and height
 * 
 * @param width
 * @param height
 */
public void resizeText(boolean increase) {
    CharSequence text = getText();
    // Do not resize if the view does not have dimensions or there is no
    // text
    if (text == null || text.length() == 0 || heightLimit <= 0 || widthLimit <= 0 || mTextSize == 0) {
        return;
    }

    // Get the text view's paint object
    TextPaint textPaint = getPaint();

    // Get the required text height
    int textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);


    // If the text length is increased 
    // Until we either fit within our text view or we had reached our min
    // text size, incrementally try smaller sizes
    if (increase) {
        while (textHeight > heightLimit && mTextSize > mMinTextSize) {
            mTextSize = Math.max(mTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
        }
    } 
//      text length has been decreased
    else {
//          if max test size is set then add it to while condition
        if (mMaxTextSize != 0) {
            while (textHeight < heightLimit && mTextSize <= mMaxTextSize) {
                mTextSize = mTextSize + 2;
                textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
            }
        } else {
            while (textHeight < heightLimit) {
                mTextSize = mTextSize + 2;
                textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);
            }
        }
        mTextSize = textHeight > heightLimit ? mTextSize - 2 : mTextSize;
    }

    // If we had reached our minimum text size and still don't fit, append
    // an ellipsis
    if (mAddEllipsis && mTextSize == mMinTextSize && textHeight > heightLimit) {
        // Draw using a static layout
        TextPaint paint = new TextPaint(textPaint);
        StaticLayout layout = new StaticLayout(text, paint, widthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
                mSpacingAdd, false);
        // Check that we have a least one line of rendered text
        if (layout.getLineCount() > 0) {
            // Since the line at the specific vertical position would be cut
            // off,
            // we must trim up to the previous line
            int lastLine = layout.getLineForVertical(heightLimit) - 1;
            // If the text would not even fit on a single line, clear it
            if (lastLine < 0) {
                setText("");
            }
            // Otherwise, trim to the previous line and add an ellipsis
            else {
                int start = layout.getLineStart(lastLine);
                int end = layout.getLineEnd(lastLine);
                float lineWidth = layout.getLineWidth(lastLine);
                float ellipseWidth = paint.measureText(mEllipsis);

                // Trim characters off until we have enough room to draw the
                // ellipsis
                while (widthLimit < lineWidth + ellipseWidth) {
                    lineWidth = paint.measureText(text.subSequence(start, --end + 1).toString());
                }
                setText(text.subSequence(0, end) + mEllipsis);
            }
        }
    }

    // Some devices try to auto adjust line spacing, so force default line
    // spacing
    // and invalidate the layout as a side effect
    setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
    setLineSpacing(mSpacingAdd, mSpacingMult);

}

// Set the text size of the text paint object and use a static layout to
// render text off screen before measuring
private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
    // Update the text paint object
    TextPaint paint = new TextPaint(originalPaint);
    paint.setTextSize(textSize);
    // Measure using a static layout
    StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd,
            true);
    return layout.getHeight();
}

}
2
ответ дан granko87 21 August 2018 в 04:30
поделиться

Расширить 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);
  }
0
ответ дан Greg Bacchus 21 August 2018 в 04:30
поделиться
  • 1
    Откуда rect? – ThomasW 16 October 2017 в 08:45
  • 2
    Вы должны объявить и создать его за пределами onDraw, так как вы должны минимизировать количество объектов, созданных внутри onDraw. Затем значение устанавливается getTextBounds. – Greg Bacchus 16 October 2017 в 08:54

Моя реализация немного сложнее, но имеет следующие достоинства:

  • учитывает доступную ширину и доступную высоту
  • работает с однострочной и многострочной метки
  • используют эллипсис в случае, если минимальный размер шрифта поражен
  • , поскольку внутреннее текстовое представление изменено, запоминает исходный текст в отдельной переменной
  • что холст всегда такой же большой, как он должен быть, в то время как он использует всю доступную высоту родительского
/**
 * Text view that auto adjusts text size to fit within the view. If the text
 * size equals the minimum text size and still does not fit, append with an
 * ellipsis.
 * 
 * Based on the original work from Chase Colburn
 * &lt;http://stackoverflow.com/a/5535672/305532>
 *
 * @author Thomas Keller &lt;me@thomaskeller.biz>
 */
public class AutoResizeTextView extends TextView {

    // in dip
    private static final int MIN_TEXT_SIZE = 20;

    private static final boolean SHRINK_TEXT_SIZE = true;

    private static final char ELLIPSIS = '\u2026';

    private static final float LINE_SPACING_MULTIPLIER_MULTILINE = 0.8f;

    private static final float LINE_SPACING_MULTIPLIER_SINGLELINE = 1f;

    private static final float LINE_SPACING_EXTRA = 0.0f;

    private CharSequence mOriginalText;

    // temporary upper bounds on the starting text size
    private float mMaxTextSize;

    // lower bounds for text size
    private float mMinTextSize;

    // determines whether we're currently in the process of measuring ourselves,
    // so we do not enter onMeasure recursively
    private boolean mInMeasure = false;

    // if the text size should be shrinked or if the text size should be kept
    // constant and only characters should be removed to hit the boundaries
    private boolean mShrinkTextSize;

    public AutoResizeTextView(Context context) {
        this(context, null);
        init(context, null);
    }

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

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

    private void init(Context context, AttributeSet attrs) {
        // the current text size is used as maximum text size we can apply to
        // our widget
        mMaxTextSize = getTextSize();
        if (attrs != null) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoResizeTextView);
            mMinTextSize = a.getFloat(R.styleable.AutoResizeTextView_minFontSize, MIN_TEXT_SIZE);
            mShrinkTextSize = a.getBoolean(R.styleable.AutoResizeTextView_shrinkTextSize, SHRINK_TEXT_SIZE);
            a.recycle();
        }
    }

    @Override
    public void setTextSize(float size) {
        mMaxTextSize = size;
        super.setTextSize(size);
    }

    /**
     * Returns the original, unmodified text of this widget
     * 
     * @return
     */
    public CharSequence getOriginalText() {
        // text has not been resized yet
        if (mOriginalText == null) {
            return getText();
        }
        return mOriginalText;
    }

    @Override
    public void setText(CharSequence text, BufferType type) {
        if (!mInMeasure) {
            mOriginalText = text.toString();
        }
        super.setText(text, type);
    }

    @SuppressLint("DrawAllocation")
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        mInMeasure = true;
        try {
            int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            int availableHeight = MeasureSpec.getSize(heightMeasureSpec) - getCompoundPaddingTop()
                    - getCompoundPaddingBottom();

            // Do not resize if the view does not have dimensions or there is no
            // text
            if (mOriginalText == null || mOriginalText.length() == 0 || availableWidth <= 0) {
                return;
            }

            TextPaint textPaint = getPaint();

            // start with the recorded max text size
            float targetTextSize = mMaxTextSize;
            String originalText = mOriginalText.toString();
            String finalText = originalText;

            Rect textSize = getTextSize(originalText, textPaint, targetTextSize);
            boolean textExceedsBounds = textSize.height() > availableHeight || textSize.width() > availableWidth;
            if (mShrinkTextSize && textExceedsBounds) {
                // check whether all lines can be rendered in the available
                // width / height without violating the bounds of the parent and
                // without using a text size that is smaller than the minimum
                // text size
                float heightMultiplier = availableHeight / (float) textSize.height();
                float widthMultiplier = availableWidth / (float) textSize.width();
                float multiplier = Math.min(heightMultiplier, widthMultiplier);
                targetTextSize = Math.max(targetTextSize * multiplier, mMinTextSize);

                // measure again
                textSize = getTextSize(finalText, textPaint, targetTextSize);
            }

            // we cannot shrink the height further when we hit the available
            // height, but we can shrink the width by applying an ellipsis on
            // each line
            if (textSize.width() > availableWidth) {
                StringBuilder modifiedText = new StringBuilder();
                String lines[] = originalText.split(System.getProperty("line.separator"));
                for (int i = 0; i < lines.length; i++) {
                    modifiedText.append(resizeLine(textPaint, lines[i], availableWidth));
                    // add the separator back to all but the last processed line
                    if (i != lines.length - 1) {
                        modifiedText.append(System.getProperty("line.separator"));
                    }
                }
                finalText = modifiedText.toString();

                // measure again
                textSize = getTextSize(finalText, textPaint, targetTextSize);
            }

            textPaint.setTextSize(targetTextSize);
            boolean isMultiline = finalText.indexOf('\n') > -1;
            // do not include extra font padding (for accents, ...) for
            // multiline texts, this will prevent proper placement with
            // Gravity.CENTER_VERTICAL
            if (isMultiline) {
                setLineSpacing(LINE_SPACING_EXTRA, LINE_SPACING_MULTIPLIER_MULTILINE);
                setIncludeFontPadding(false);
            } else {
                setLineSpacing(LINE_SPACING_EXTRA, LINE_SPACING_MULTIPLIER_SINGLELINE);
                setIncludeFontPadding(true);
            }

            // according to
            // <http://code.google.com/p/android/issues/detail?id=22493>
            // we have to add a unicode character to trigger the text centering
            // in ICS. this particular character is known as "zero-width" and
            // does no harm.
            setText(finalText + "\u200B");

            int measuredWidth = textSize.width() + getCompoundPaddingLeft() + getCompoundPaddingRight();
            int measuredHeight = textSize.height() + getCompoundPaddingTop() + getCompoundPaddingBottom();

            // expand the view to the parent's height in case it is smaller or
            // to the minimum height that has been set
            // FIXME: honor the vertical measure mode (EXACTLY vs AT_MOST) here
            // somehow
            measuredHeight = Math.max(measuredHeight, MeasureSpec.getSize(heightMeasureSpec));
            setMeasuredDimension(measuredWidth, measuredHeight);
        } finally {
            mInMeasure = false;
        }
    }

    private Rect getTextSize(String text, TextPaint textPaint, float textSize) {
        textPaint.setTextSize(textSize);
        // StaticLayout depends on a given width in which it should lay out the
        // text (and optionally also split into separate lines).
        // Therefor we calculate the current text width manually and start with
        // a fake (read: maxmimum) width for the height calculation.
        // We do _not_ use layout.getLineWidth() here since this returns
        // slightly smaller numbers and therefor would lead to exceeded text box
        // drawing.
        StaticLayout layout = new StaticLayout(text, textPaint, Integer.MAX_VALUE, Alignment.ALIGN_NORMAL, 1f, 0f, true);
        int textWidth = 0;
        String lines[] = text.split(System.getProperty("line.separator"));
        for (int i = 0; i < lines.length; ++i) {
            textWidth = Math.max(textWidth, measureTextWidth(textPaint, lines[i]));
        }
        return new Rect(0, 0, textWidth, layout.getHeight());
    }

    private String resizeLine(TextPaint textPaint, String line, int availableWidth) {
        checkArgument(line != null && line.length() > 0, "expected non-empty string");
        int textWidth = measureTextWidth(textPaint, line);
        int lastDeletePos = -1;
        StringBuilder builder = new StringBuilder(line);
        while (textWidth > availableWidth && builder.length() > 0) {
            lastDeletePos = builder.length() / 2;
            builder.deleteCharAt(builder.length() / 2);
            // don't forget to measure the ellipsis character as well; it
            // doesn't matter where it is located in the line, it just has to be
            // there, since there are no (known) ligatures that use this glyph
            String textToMeasure = builder.toString() + ELLIPSIS;
            textWidth = measureTextWidth(textPaint, textToMeasure);
        }
        if (lastDeletePos > -1) {
            builder.insert(lastDeletePos, ELLIPSIS);
        }
        return builder.toString();
    }

    // there are several methods in Android to determine the text width, namely
    // getBounds() and measureText().
    // The latter works for us the best as it gives us the best / nearest
    // results without that our text canvas needs to wrap its text later on
    // again.
    private int measureTextWidth(TextPaint textPaint, String line) {
        return Math.round(textPaint.measureText(line));
    }
}

[пересмотрен в 2012-11-21]

  • исправлено размещение эллипсиса (ошибка «один за другим»)
  • переработанный размер размера текста; теперь всегда выполняется полный текст, включая разрывы строк, для устранения проблем, когда добавление высоты двух отдельных измеряемых строк просто не привело к такому же результату, как измерение высоты текста в целом
  • вместо петлирования, чтобы найти наименьший доступный размер текста, просто вычислите его после первого измерения
40
ответ дан Idolon 21 August 2018 в 04:30
поделиться
  • 1
    Интересно, почему это еще не было? Это, конечно, взлом по натуре, но очень простое решение. Для приложения, которое выкладывает только несколько прокручиваемых SimpleLineTextViews, это самый простой способ. Отличная находка! – Tom 24 January 2013 в 21:19
  • 2
    Что такое StyleableTextView? – Tushar 22 February 2013 в 23:38
  • 3
    Внутренний класс, извините, я удалю это здесь. – Thomas Keller 25 February 2013 в 15:22
  • 4
    исправить простые ошибки в вашем коде – pleerock 6 March 2013 в 09:29
  • 5
    полный ошибок и все еще содержит ссылки на StylableTextView – LairdPleng 30 May 2013 в 05:46
  • 6
    Не работает. Пробовал это на Android 4.1 – AndroidDev 30 July 2013 в 11:38
  • 7
    похоже, не работает с layout_weight – Malachiasz 18 February 2014 в 11:59
  • 8
    @SergioCarneiro nice find, но super.onMeasure(widthMeasureSpec, heightMeasureSpec); не вызывает рекурсии, но measure(widthMeasureSpec, heightMeasureSpec);. Поэтому я изменил на «меру (widthMeasureSpec, heightMeasureSpec); & quot; и это работает. Я только что протестировал на Android 4.4 (Nexus 5) и Android 4.0.4 (Samsung SII-LTE), и он отлично справился с обоими из них (я даже использовал собственный японский шрифт OTF). Захватите мое исправление здесь: gist.github.com/mrleolink/0dfeef749da1b854a44b – Leo 30 June 2014 в 13:06
  • 9
    лучший ответ. используя версию LeoLink, она хорошо работает на моих эмуляторах 2.3 и 4.0 и реальных устройствах. если они используются в списке, не забудьте вызвать requestLayout () после установки текста. – adbie 20 January 2015 в 12:41
  • 10
    Я имею forked версию @LeoLink, она использует maxLines и requestLayout() на setText, как сказал @adbie. FixedLineTextView - & gt; gist.github.com/Kevinrob/09742d9069e4e4e4ab66 – Kevin Robatel 24 April 2015 в 07:57

Вот простое решение, которое использует TextView непосредственно с добавленным TextChangedListened:

expressionView = (TextView) findViewById(R.id.expressionView);
expressionView.addTextChangedListener(textAutoResizeWatcher(expressionView, 25, 55));

private TextWatcher textAutoResizeWatcher(final TextView view, final int MIN_SP, final int MAX_SP) {
    return new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

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

        @Override
        public void afterTextChanged(Editable editable) {

            final int widthLimitPixels = view.getWidth() - view.getPaddingRight() - view.getPaddingLeft();
            Paint paint = new Paint();
            float fontSizeSP = pixelsToSp(view.getTextSize());
            paint.setTextSize(spToPixels(fontSizeSP));

            String viewText = view.getText().toString();

            float widthPixels = paint.measureText(viewText);

            // Increase font size if necessary.
            if (widthPixels < widthLimitPixels){
                while (widthPixels < widthLimitPixels && fontSizeSP <= MAX_SP){
                    ++fontSizeSP;
                    paint.setTextSize(spToPixels(fontSizeSP));
                    widthPixels = paint.measureText(viewText);
                }
                --fontSizeSP;
            }
            // Decrease font size if necessary.
            else {
                while (widthPixels > widthLimitPixels || fontSizeSP > MAX_SP) {
                    if (fontSizeSP < MIN_SP) {
                        fontSizeSP = MIN_SP;
                        break;
                    }
                    --fontSizeSP;
                    paint.setTextSize(spToPixels(fontSizeSP));
                    widthPixels = paint.measureText(viewText);
                }
            }

            view.setTextSize(fontSizeSP);
        }
    };
}

private float pixelsToSp(float px) {
    float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
    return px/scaledDensity;
}

private float spToPixels(float sp) {
    float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
    return sp * scaledDensity;
}

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

7
ответ дан ilharco 21 August 2018 в 04:30
поделиться
  • 1
    для меня это лучший и приятный ответ. Я использовал пользовательский шрифт, поэтому мне нужно просто добавить paint.setTypeface (typeFace); работает как волшебство .. – Abhijit 5 November 2016 в 18:57

Мне нужно было изменить размер текста, чтобы он идеально соответствовал рамкам обзора. Решение Chase только уменьшает размер текста, и это увеличивает текст, если есть достаточно места.

Чтобы сделать все быстрым & amp; Точный я использовал метод деления пополам вместо итеративного, как вы можете видеть в методе resizeText(). Вот почему у вас также есть опция MAX_TEXT_SIZE. Я также включил советы Onoelle.

Протестировано на Android 4.4

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT YOU WANT TO.
 */

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

/**
 * Text view that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 *
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 26;

    // Maximum text size for this text view
    public static final float MAX_TEXT_SIZE = 128;

    private static final int BISECTION_LOOP_WATCH_DOG = 30;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = MAX_TEXT_SIZE;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if(mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            //mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if(changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // Bisection method: fast & precise
        float lower = mMinTextSize;
        float upper = mMaxTextSize;
        int loop_counter=1;
        float targetTextSize = (lower+upper)/2;
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        while(loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
            targetTextSize = (lower+upper)/2;
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            if(textHeight > height)
                upper = targetTextSize;
            else
                lower = targetTextSize;
            loop_counter++;
        }

        targetTextSize = lower;
        textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paintCopy = new TextPaint(textPaint);
            paintCopy.setTextSize(targetTextSize);
            StaticLayout layout = new StaticLayout(text, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if(layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if(lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = paintCopy.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while(width < lineWidth + ellipseWidth) {
                        lineWidth = paintCopy.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if(mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paint = new TextPaint(originalPaint);
        // Update the text paint object
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}
10
ответ дан kenorb 21 August 2018 в 04:30
поделиться
  • 1
    Это работает! также решают некоторые проблемы из исходного кода. – Nick Jian 14 January 2014 в 11:06
  • 2
    не работает в Android 4.03 в 4.4 работах – Malachiasz 18 February 2014 в 11:11

Я написал сообщение в блоге об этом.

Я создал компонент под названием ResizableButton, основанный на записи блога Кирилла Гроучникова о пользовательских компонентах, используемых в новом приложении для Android. Я поместил здесь код src .

С другой стороны, mosabua прочитал мой пост и сказал мне, что он собирается открыть исходный код своей реализации, который был быстрее, чем у меня. Надеюсь, он скоро выпустит:)

7
ответ дан Macarse 21 August 2018 в 04:30
поделиться
  • 1
    Это работает для одной строки текста, но не работает для обертывания текста. Разве что-то я пропустил? measure (), по-видимому, измеряет ширину в предположении, что весь текст будет в одной строке. – Nathan Fig 15 March 2011 в 20:25
  • 2
    Я заметил, что ваша реализация не центрирует текст по умолчанию. Он плавает влево. Мысли? – Donn Felker 31 May 2011 в 22:33
  • 3
    заметил, что у меня была такая же проблема, когда текст не был центрирован, я добавил строку android: singleLine = & quot; true & quot; и после этого – clayton33 7 June 2012 в 11:03
  • 4
    Вы можете опубликовать этот фрагмент кода на GitHub, это очень полезно. Я действительно ненавижу систему макетов Android, не позволяя вам автоматически изменять размеры шрифтов в элементах. – Lachezar 21 October 2015 в 13:24

AppcompatTextView теперь поддерживает автоматическое определение размера, начиная с Support Library 26.0. TextView в Android O также работает одинаково. Более подробную информацию можно найти здесь. Здесь доступно простое демо-приложение . .

<LinearLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:autoSizeTextType="uniform"
        app:autoSizeMinTextSize="12sp"
        app:autoSizeMaxTextSize="100sp"
        app:autoSizeStepGranularity="2sp"
      />

</LinearLayout>
10
ответ дан makata 21 August 2018 в 04:30
поделиться
  • 1
    Мне пришлось установить android:lines="1" в TextView, чтобы изменить размер текста. Без этого атрибута текст был обернут в две строки. – rewgoes 21 May 2017 в 14:30
  • 2
    каков импорт, чтобы сделать эту работу? Я получаю ошибки сборки при попытке 26.0.0-beta-1 – Psest328 1 June 2017 в 14:29
  • 3
    @ Psest328, вы используете maven {url & quot; maven.google.com & quot; / g0]; }? – makata 11 July 2017 в 15:27
  • 4
    @makata Я не уверен, но это отличный момент. +1 – Psest328 12 July 2017 в 19:23

Предупреждение, ошибка в Android Honeycomb и Ice Cream Sandwich

Варианты андроидов: 3.1 - 4.04 имеют ошибку, что setTextSize () внутри TextView работает только в первый раз (1-й вызов).

Ошибка описана здесь: http://code.google.com/p/android/issues/detail?id=22493 http://code.google.com / p / android / issues / detail? id = 17343 # c9

обходным путем является добавление нового символа строки в текст, назначенный TextView, перед изменением размера:

final String DOUBLE_BYTE_SPACE = "\u3000";
textView.append(DOUBLE_BYTE_SPACE);

Я использую его в своем коде следующим образом:

final String DOUBLE_BYTE_SPACE = "\u3000";
AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
String fixString = "";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1
   && android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {  
    fixString = DOUBLE_BYTE_SPACE;
}
textView.setText(fixString + "The text" + fixString);

Я добавляю этот символ «\ u3000» слева и справа от моего текста, чтобы поддерживать его в центре. Если вы выровнены по левому краю, добавьте его справа. Конечно, он также может быть встроен с виджетами AutoResizeTextView, но я хотел бы сохранить исправленный код снаружи.

16
ответ дан Malachiasz 21 August 2018 в 04:30
поделиться
  • 1
    Добавлен ⁠ & quot; \ u2060 & quot; чтобы текст был по центру вертикально. – igla 15 July 2015 в 10:00

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

Примечание: этот код был взят непосредственно из Google Android Lollipop dialer некоторое время назад, я не помню Если изменения были сделаны в то время. Кроме того, я не знаю, какая из этих лицензий существует, но у меня есть причина думать, что это Apache 2.0.

Класс ResizeTextView, фактический View

public class ResizeTextView extends TextView {

private final int mOriginalTextSize;
private final int mMinTextSize;
private final static int sMinSize = 20;
public ResizeTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mOriginalTextSize = (int) getTextSize();
    mMinTextSize = (int) sMinSize;
}
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
    super.onTextChanged(text, start, lengthBefore, lengthAfter);
    ViewUtil.resizeText(this, mOriginalTextSize, mMinTextSize);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    ViewUtil.resizeText(this, mOriginalTextSize, mMinTextSize);
}

Этот класс ResizeTextView может расширять TextView и все его дочерние элементы, как я понял, так и EditText.

Класс ViewUtil с методом resizeText(...)

/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import android.graphics.Paint;
import android.util.TypedValue;
import android.widget.TextView;

public class ViewUtil {

    private ViewUtil() {}

    public static void resizeText(TextView textView, int originalTextSize, int minTextSize) {
        final Paint paint = textView.getPaint();
        final int width = textView.getWidth();
        if (width == 0) return;
        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalTextSize);
        float ratio = width / paint.measureText(textView.getText().toString());
        if (ratio <= 1.0f) {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                    Math.max(minTextSize, originalTextSize * ratio));
        }
    }
}

Вы должен установить ваше представление как

<yourpackage.yourapp.ResizeTextView
            android:layout_width="match_parent"
            android:layout_height="64dp"
            android:gravity="center"
            android:maxLines="1"/>

Надеюсь, что это поможет!

8
ответ дан Matteo 21 August 2018 в 04:30
поделиться
  • 1
    получить ширину всегда 0 – Dr. aNdRO 29 August 2016 в 08:20
  • 2
    Он не предназначен для того, чтобы дать вам действительную ширину, поскольку он должен автоматически изменяться. Вам все равно нужно вызвать ViewTreeObserver, если вы хотите получить фактическую ширину представления. – Matteo 29 August 2016 в 08:42
  • 3
    Ваше решение не масштабируется оптимально. Слишком много пробелов внутри TextView. – CoolMind 9 December 2016 в 15:19

На конференции google IO в 2017 году google представила свойство autoSize TextView

https://youtu.be/fjUdJ2aVqE4

<android.support.v7.widget.AppCompatTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/my_text"
        app:autoSizeTextType="uniform"
        app:autoSizeMaxTextSize="10sp"
        app:autoSizeMinTextSize="6sp"
        app:autoSizeStepGranularity="1sp"/>
6
ответ дан MiladAhmadi 21 August 2018 в 04:30
поделиться
  • 1
    android.support.v7.widget.AppCompatTextView важен. Он не работает, как app: autoSizeTextType атрибуты на стандартном TextView. Поздравляем ... – Sinan Ergin 1 November 2017 в 21:30
  • 2
    работает на меня! должен добавить приложение: autoSizeTextType = & quot; равномерное & quot; линия – avisper 16 July 2018 в 12:42

Если кому-то это нужно, вот такой же фрагмент кода, но для Xamarin.Android.

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 1, December 2017
 * 
 * Copyright (C) 2017 Nathan Westfall
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */

using Android.Content;
using Android.Runtime;
using Android.Widget;
using Android.Util;
using Android.Text;

using Java.Lang;

namespace My.Text
{
    public class AutoResizeTextView : TextView
    {
        public const float MIN_TEXT_SIZE = 20;

        public interface OnTextResizeListener
        {
            void OnTextResize(TextView textView, float oldSize, float newSize);
        }

        private const string mEllipsis = "...";

        private OnTextResizeListener mTextResizeListener;

        private bool mNeedsResize = false;

        private float mTextSize;

        private float mMaxTextSize = 0;

        private float mMinTextSize = MIN_TEXT_SIZE;

        private float mSpacingMult = 1.0f;

        private float mSpacingAdd = 0.0f;

        public bool AddEllipsis { get; set; } = true;

        public AutoResizeTextView(Context context) : this(context, null) { }

        public AutoResizeTextView(Context context, IAttributeSet attrs) : this(context, attrs, 0) { }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyle): base(context, attrs, defStyle)
        {
            mTextSize = TextSize;
        }

        protected override void OnTextChanged(ICharSequence text, int start, int lengthBefore, int lengthAfter)
        {
            base.OnTextChanged(text, start, lengthBefore, lengthAfter);

            mNeedsResize = true;

            ResetTextSize();
        }

        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            base.OnSizeChanged(w, h, oldw, oldh);

            if (w != oldw || h != oldh)
                mNeedsResize = true;
        }

        public void SetOnResizeListener(OnTextResizeListener listener)
        {
            mTextResizeListener = listener;
        }

        public override void SetTextSize([GeneratedEnum] ComplexUnitType unit, float size)
        {
            base.SetTextSize(unit, size);

            mTextSize = TextSize;
        }

        public override void SetLineSpacing(float add, float mult)
        {
            base.SetLineSpacing(add, mult);

            mSpacingMult = mult;
            mSpacingAdd = add;
        }

        public void SetMaxTextSize(float maxTextSize)
        {
            mMaxTextSize = maxTextSize;
            RequestLayout();
            Invalidate();
        }

        public float GetMaxTextSize()
        {
            return mMaxTextSize;
        }

        public void SetMinTextSize(float minTextSize)
        {
            mMinTextSize = minTextSize;
            RequestLayout();
            Invalidate();
        }

        public float GetMinTextSize()
        {
            return mMinTextSize;
        }

        public void ResetTextSize()
        {
            if(mTextSize > 0)
            {
                base.SetTextSize(ComplexUnitType.Px, mTextSize);
                mMaxTextSize = mTextSize;
            }
        }

        protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
        {
            if(changed || mNeedsResize)
            {
                int widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
                int heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
                ResizeText(widthLimit, heightLimit);
            }
            base.OnLayout(changed, left, top, right, bottom);

            base.OnLayout(changed, left, top, right, bottom);
        }

        public void ResizeText()
        {
            int heightLimit = Height - PaddingBottom - PaddingTop;
            int widthLimit = Width - PaddingLeft - PaddingRight;
            ResizeText(widthLimit, heightLimit);
        }

        public void ResizeText(int width, int height)
        {
            var text = TextFormatted;
            if (text == null || text.Length() == 0 || height <= 0 || width <= 0 || mTextSize == 0)
                return;

            if (TransformationMethod != null)
                text = TransformationMethod.GetTransformationFormatted(TextFormatted, this);

            TextPaint textPaint = Paint;

            float oldTextSize = textPaint.TextSize;
            float targetTextSize = mMaxTextSize > 0 ? System.Math.Min(mTextSize, mMaxTextSize) : mTextSize;

            int textHeight = GetTextHeight(text, textPaint, width, targetTextSize);

            while(textHeight > height && targetTextSize > mMinTextSize)
            {
                targetTextSize = System.Math.Max(targetTextSize - 2, mMinTextSize);
                textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
            }

            if(AddEllipsis && targetTextSize == mMinTextSize && textHeight > height)
            {
                TextPaint paint = new TextPaint(textPaint);

                StaticLayout layout = new StaticLayout(text, paint, width, Layout.Alignment.AlignNormal, mSpacingMult, mSpacingAdd, false);
                if(layout.LineCount > 0)
                {
                    int lastLine = layout.GetLineForVertical(height) - 1;
                    if (lastLine < 0)
                        SetText("", BufferType.Normal);
                    else
                    {
                        int start = layout.GetLineStart(lastLine);
                        int end = layout.GetLineEnd(lastLine);
                        float lineWidth = layout.GetLineWidth(lastLine);
                        float ellipseWidth = textPaint.MeasureText(mEllipsis);

                        while (width < lineWidth + ellipseWidth)
                            lineWidth = textPaint.MeasureText(text.SubSequence(start, --end + 1).ToString());
                        SetText(text.SubSequence(0, end) + mEllipsis, BufferType.Normal);
                    }
                }
            }

            SetTextSize(ComplexUnitType.Px, targetTextSize);
            SetLineSpacing(mSpacingAdd, mSpacingMult);

            mTextResizeListener?.OnTextResize(this, oldTextSize, targetTextSize);

            mNeedsResize = false;
        }

        private int GetTextHeight(ICharSequence source, TextPaint paint, int width, float textSize)
        {
            TextPaint paintCopy = new TextPaint(paint);
            paintCopy.TextSize = textSize;
            StaticLayout layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, mSpacingMult, mSpacingAdd, false);
            return layout.Height;
        }
    }
}
1
ответ дан nwestfall 21 August 2018 в 04:30
поделиться

Предоставление этой версии верхнего ответа, переписанного на C #, для тех, кто кодов на Xamarin.Android. Работал для меня хорошо.

 /**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */


using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Util;
using Android.Widget;
using Java.Lang;

namespace App.GuestGuide.Droid.Controls
{
    public class OnTextResizeEventArgs : EventArgs
    {
        public TextView TextView { get; set; }
        public float OldSize { get; set; }
        public float NewSize { get; set; }
    }

    /// <inheritdoc />
    /// <summary>
    /// Text view that auto adjusts text size to fit within the view.
    /// If the text size equals the minimum text size and still does not
    /// fit, append with an ellipsis.
    /// </summary>
    public class AutoResizeTextView : TextView
    {
        /// <summary>
        /// Minimum text size for this text view
        /// </summary>
        public static float MIN_TEXT_SIZE = 10;

        /// <summary>
        /// Our ellipse string
        /// </summary>
        private const string Ellipsis = "...";


        private float _mMaxTextSize;

        private float _mMinTextSize = MIN_TEXT_SIZE;

        /// <summary>
        /// Register subscriber to receive resize notifications
        /// </summary>
        public event EventHandler<OnTextResizeEventArgs> OnTextResize;

        /// <summary>
        /// Flag for text and/or size changes to force a resize
        /// </summary>
        private bool _needsResize;

        /// <summary>
        /// Text size that is set from code. This acts as a starting point for resizing
        /// </summary>
        private float _textSize;

        /// <summary>
        /// Text view line spacing multiplier
        /// </summary>
        private float _spacingMult = 1.0f;

        /// <summary>
        /// Text view additional line spacing
        /// </summary>
        private float _spacingAdd;

        /// <summary>
        /// Add ellipsis to text that overflows at the smallest text size
        /// </summary>
        public bool ShouldAddEllipsis { get; set; }

        /// <inheritdoc />
        /// <summary>
        /// Override the set text size to update our internal reference values
        /// </summary>
        public override float TextSize
        {
            get => base.TextSize;
            set
            {
                base.TextSize = value;
                _textSize = TextSize;
            }
        }

        /// <summary>
        /// Temporary upper bounds on the starting text size
        /// </summary>
        public float MaxTextSize
        {
            get => _mMaxTextSize;
            // Set the upper text size limit and invalidate the view
            set
            {
                _mMaxTextSize = value;
                RequestLayout();
                Invalidate();
            }
        }

        /// <summary>
        /// Lower bounds for text size
        /// </summary>
        public float MinTextSize
        {
            get => _mMinTextSize;
            //Set the lower text size limit and invalidate the view
            set
            {
                _mMinTextSize = value;
                RequestLayout();
                Invalidate();
            }
        }

        public AutoResizeTextView(Context context) : this(context, null)
        {
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs) : this(context, attrs, 0)
        {
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
        {
            _textSize = TextSize;
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
        {
            _textSize = TextSize;
        }

        protected AutoResizeTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
            _textSize = TextSize;
        }

        /// <inheritdoc />
        /// <summary>
        /// When text changes, set the force resize flag to true and reset the text size.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="start"></param>
        /// <param name="lengthBefore"></param>
        /// <param name="lengthAfter"></param>
        protected override void OnTextChanged(ICharSequence text, int start, int lengthBefore, int lengthAfter)
        {
            _needsResize = true;
            // Since this view may be reused, it is good to reset the text size
            ResetTextSize();
        }

        /// <inheritdoc />
        /// <summary>
        /// If the text view size changed, set the force resize flag to true
        /// </summary>
        /// <param name="w"></param>
        /// <param name="h"></param>
        /// <param name="oldw"></param>
        /// <param name="oldh"></param>
        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            if (w != oldw || h != oldh)
            {
                _needsResize = true;
            }
        }

        public override void SetTextSize([GeneratedEnum] ComplexUnitType unit, float size)
        {
            base.SetTextSize(unit, size);
            _textSize = TextSize;
        }

        /// <inheritdoc />
        /// <summary>
        /// Override the set line spacing to update our internal reference values
        /// </summary>
        /// <param name="add"></param>
        /// <param name="mult"></param>
        public override void SetLineSpacing(float add, float mult)
        {
            base.SetLineSpacing(add, mult);
            _spacingMult = mult;
            _spacingAdd = add;
        }

        /// <summary>
        /// Reset the text to the original size
        /// </summary>
        public void ResetTextSize()
        {
            if (_textSize > 0)
            {
                base.SetTextSize(ComplexUnitType.Px, _textSize);
                _mMaxTextSize = _textSize;
            }
        }

        /// <inheritdoc />
        /// <summary>
        /// Resize text after measuring
        /// </summary>
        /// <param name="changed"></param>
        /// <param name="left"></param>
        /// <param name="top"></param>
        /// <param name="right"></param>
        /// <param name="bottom"></param>
        protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
        {
            if (changed || _needsResize)
            {
                var widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
                var heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
                ResizeText(widthLimit, heightLimit);
            }

            base.OnLayout(changed, left, top, right, bottom);
        }

        /// <summary>
        /// Resize the text size with default width and height
        /// </summary>
        public void ResizeText()
        {
            var heightLimit = Height - PaddingBottom - PaddingTop;
            var widthLimit = Width - PaddingLeft - PaddingRight;
            ResizeText(widthLimit, heightLimit);
        }

        /// <summary>
        /// Resize the text size with specified width and height
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void ResizeText(int width, int height)
        {
            ICharSequence text = null;

            if (!string.IsNullOrEmpty(Text))
            {
                text = new Java.Lang.String(Text);
            }

            // Do not resize if the view does not have dimensions or there is no text
            if (text == null || text.Length() == 0 || height <= 0 || width <= 0 || _textSize == 0)
            {
                return;
            }

            if (TransformationMethod != null)
            {
                text = TransformationMethod.GetTransformationFormatted(text, this);
            }

            // Get the text view's paint object
            var textPaint = Paint;
            // Store the current text size
            var oldTextSize = textPaint.TextSize;
            // If there is a max text size set, use the lesser of that and the default text size
            var targetTextSize = _mMaxTextSize > 0 ? System.Math.Min(_textSize, _mMaxTextSize) : _textSize;

            // Get the required text height
            var textHeight = GetTextHeight(text, textPaint, width, targetTextSize);

            // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
            while (textHeight > height && targetTextSize > _mMinTextSize)
            {
                targetTextSize = System.Math.Max(targetTextSize - 2, _mMinTextSize);
                textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
            }

            // If we had reached our minimum text size and still don't fit, append an ellipsis
            if (ShouldAddEllipsis && targetTextSize == _mMinTextSize && textHeight > height)
            {
                // Draw using a static layout
                // modified: use a copy of TextPaint for measuring
                var paint = new TextPaint(textPaint);
                // Draw using a static layout
                var layout = new StaticLayout(text, paint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, false);

                // Check that we have a least one line of rendered text
                if (layout.LineCount > 0)
                {
                    // Since the line at the specific vertical position would be cut off,
                    // we must trim up to the previous line
                    var lastLine = layout.GetLineForVertical(height) - 1;
                    // If the text would not even fit on a single line, clear it
                    if (lastLine < 0)
                    {
                        Text = string.Empty;
                    }
                    // Otherwise, trim to the previous line and add an ellipsis
                    else
                    {
                        var start = layout.GetLineStart(lastLine);
                        var end = layout.GetLineEnd(lastLine);
                        var lineWidth = layout.GetLineWidth(lastLine);
                        var ellipseWidth = textPaint.MeasureText(Ellipsis);

                        // Trim characters off until we have enough room to draw the ellipsis
                        while (width < lineWidth + ellipseWidth)
                        {
                            lineWidth = textPaint.MeasureText(text.SubSequence(start, --end + 1));
                        }

                        Text = text.SubSequence(0, end) + Ellipsis;
                    }
                }
            }

            // Some devices try to auto adjust line spacing, so force default line spacing
            // and invalidate the layout as a side effect
            SetTextSize(ComplexUnitType.Px, targetTextSize);
            SetLineSpacing(_spacingAdd, _spacingMult);

            var notifyArgs = new OnTextResizeEventArgs
            {
                TextView = this,
                NewSize = targetTextSize,
                OldSize = oldTextSize
            };

            // Notify the listener if registered
            OnTextResize?.Invoke(this, notifyArgs);

            // Reset force resize flag
            _needsResize = false;
        }

        /// <summary>
        /// Set the text size of the text paint object and use a static layout to render text off screen before measuring
        /// </summary>
        /// <param name="source"></param>
        /// <param name="paint"></param>
        /// <param name="width"></param>
        /// <param name="textSize"></param>
        /// <returns></returns>
        private int GetTextHeight(ICharSequence source, TextPaint paint, int width, float textSize)
        {
            // modified: make a copy of the original TextPaint object for measuring
            // (apparently the object gets modified while measuring, see also the
            // docs for TextView.getPaint() (which states to access it read-only)
            // Update the text paint object
            var paintCopy = new TextPaint(paint)
            {
                TextSize = textSize
            };

            // Measure using a static layout
            var layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, true);

            return layout.Height;
        }
    }
}
1
ответ дан Oleg Kosuakiv 21 August 2018 в 04:30
поделиться

Я начал с решения Chase, но мне пришлось адаптировать две вещи до того, как он работал на моем устройстве (Galaxy Nexus, Android 4.1):

  1. , используя копию TextPaint для измерения макета В документации для TextView.getPaint () указано, что он должен использоваться только для чтения, поэтому я сделал копию в обоих местах, где мы используем объект paint для измерения:
    // 1. in resizeText()
    if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
      // Draw using a static layout
      // modified: use a copy of TextPaint for measuring
      TextPaint paint = new TextPaint(textPaint);
    
    // 2. in getTextHeight()
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
      // modified: make a copy of the original TextPaint object for measuring
      // (apparently the object gets modified while measuring, see also the
      // docs for TextView.getPaint() (which states to access it read-only)
      TextPaint paint = new TextPaint(originalPaint);
      // Update the text paint object
      paint.setTextSize(textSize);
      ...
    
  2. добавление единицы для установки размера текста
    // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
    setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
    setLineSpacing(mSpacingAdd, mSpacingMult);
    

С этими двумя изменениями решение отлично работает для меня, благодаря Chase! Я не знаю, связано ли это с Android 4.x, что исходное решение не работает. Если вы хотите увидеть его в действии или проверить, действительно ли он работает на вашем устройстве, вы можете посмотреть мое приложение флеш-карты Flashcards ToGo , где я использую это решение для масштабирования текста флеш-карты. Текст может иметь произвольную длину, и карточки будут отображаться в разных действиях, иногда поменьше, иногда больше, плюс в режиме пейзажа + портрет, и я не нашел ни одного углового случая, когда решение не будет работать должным образом ...

35
ответ дан onoelle 21 August 2018 в 04:30
поделиться

Я только что создал следующий метод (основанный на идеях Chase), который может помочь вам, если вы хотите нарисовать текст на любой холст:

private static void drawText(Canvas canvas, int xStart, int yStart,
        int xWidth, int yHeigth, String textToDisplay,
        TextPaint paintToUse, float startTextSizeInPixels,
        float stepSizeForTextSizeSteps) {

    // Text view line spacing multiplier
    float mSpacingMult = 1.0f;
    // Text view additional line spacing
    float mSpacingAdd = 0.0f;
    StaticLayout l = null;
    do {
        paintToUse.setTextSize(startTextSizeInPixels);
        startTextSizeInPixels -= stepSizeForTextSizeSteps;
        l = new StaticLayout(textToDisplay, paintToUse, xWidth,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
    } while (l.getHeight() > yHeigth);

    int textCenterX = xStart + (xWidth / 2);
    int textCenterY = (yHeigth - l.getHeight()) / 2;

    canvas.save();
    canvas.translate(textCenterX, textCenterY);
    l.draw(canvas);
    canvas.restore();
}

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

1
ответ дан Simon Heinen 21 August 2018 в 04:30
поделиться
  • 1
    Создание объектов в функции рисования - плохая практика – Ofek Ron 28 May 2016 в 19:06

Эти решения работают для нас:

public class CustomFontButtonTextFit extends CustomFontButton
{
    private final float DECREMENT_FACTOR = .1f;

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

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

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

    private synchronized void refitText(String text, int textWidth) {
        if (textWidth > 0) 
        {
            float availableWidth = textWidth - this.getPaddingLeft()
                    - this.getPaddingRight();

            TextPaint tp = getPaint();
            Rect rect = new Rect();
            tp.getTextBounds(text, 0, text.length(), rect);
            float size = rect.width();

            while(size > availableWidth)
            {
                setTextSize( getTextSize() - DECREMENT_FACTOR );
                tp = getPaint();

                tp.getTextBounds(text, 0, text.length(), rect);
                size = rect.width();
            }
        }
    }

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

        if(parentWidth < getSuggestedMinimumWidth())
            parentWidth = getSuggestedMinimumWidth();

        if(parentHeight < getSuggestedMinimumHeight())
            parentHeight = getSuggestedMinimumHeight();

        this.setMeasuredDimension(parentWidth, parentHeight);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) 
    {
        super.onTextChanged(text, start, before, after);

        refitText(text.toString(), this.getWidth());
    }

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

        if (w != oldw) 
            refitText(this.getText().toString(), w);
    }
}
0
ответ дан Springfeed 21 August 2018 в 04:30
поделиться
  • 1
    Что такое CustomFontButton, от которого это распространяется? – Intrications 28 October 2012 в 22:58
  • 2
    если я продолжу CustomFontButtonTextFit из класса Button, тогда пользовательский интерфейс никогда не будет отображаться. Я бы сказал, что этот класс сломан ... – Someone Somewhere 3 April 2016 в 14:52

Вот еще одно решение, просто для пинков. Это, вероятно, не очень эффективно, но он справляется с высотой и шириной текста и с выделенным текстом.

@Override
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
{
    if ((MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED)
            && (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED)) {

        final float desiredWidth = MeasureSpec.getSize(widthMeasureSpec);
        final float desiredHeight = MeasureSpec.getSize(heightMeasureSpec);

        float textSize = getTextSize();
        float lastScale = Float.NEGATIVE_INFINITY;
        while (textSize > MINIMUM_AUTO_TEXT_SIZE_PX) {
            // Measure how big the textview would like to be with the current text size.
            super.onMeasure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);

            // Calculate how much we'd need to scale it to fit the desired size, and
            // apply that scaling to the text size as an estimate of what we need.
            final float widthScale = desiredWidth / getMeasuredWidth();
            final float heightScale = desiredHeight / getMeasuredHeight();
            final float scale = Math.min(widthScale, heightScale);

            // If we don't need to shrink the text, or we don't seem to be converging, we're done.
            if ((scale >= 1f) || (scale <= lastScale)) {
                break;
            }

            // Shrink the text size and keep trying.
            textSize = Math.max((float) Math.floor(scale * textSize), MINIMUM_AUTO_TEXT_SIZE_PX);
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            lastScale = scale;
        }
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
1
ответ дан Squatting Bear 21 August 2018 в 04:30
поделиться
  • 1
    кажется, не работает с layout_weight – Malachiasz 18 February 2014 в 11:44
  • 2
    Я пробовал 6 других ответов, но это дает мне лучшие результаты. Тем не менее это не идеально, потому что размер текста оказывается немного меньше, чем в некоторых случаях, но лучше, чем другие ответы. – ThomasW 16 October 2017 в 09:05

У меня есть код от chase и M-WaJeEh, и я нашел некоторое преимущество & amp; Недостаток здесь

from chase

Преимущество:

  • идеально подходит для 1 строки TextView

Недостаток:

  • , если это более 1 строка с пользовательским шрифтом, какой-то текст исчезнет
  • , если он активирует эллипс, он не подготовил пространство для эллипса
  • , если это пользовательский шрифт (шрифт), он не поддерживает

из M-WaJeEh

Преимущество:

  • идеально подходит для многострочных

Недостаток:

  • , если установить высоту как обертывание, этот код начнется с минимума размер и он будет уменьшаться до наименьшего, насколько это возможно, а не из setSize и уменьшить на ограниченной ширине
  • , если это пользовательский шрифт (шрифт), он не поддерживает
< / BLOCKQUOTE>
1
ответ дан Sruit A.Suk 21 August 2018 в 04:30
поделиться
  • 1
    Сбой в getTextHeight() при настройке текста или размера текста. Android 4.0.4 эмулятор. java.lang.IllegalArgumentException: Layout: -40 < 0 at android.text.Layout.<init>(Layout.java:140) at android.text.StaticLayout.<init>(StaticLayout.java:104) at android.text.StaticLayout.<init>(StaticLayout.java:90) at android.text.StaticLayout.<init>(StaticLayout.java:68) at android.text.StaticLayout.<init>(StaticLayout.java:48) – TalkLittle 4 July 2014 в 01:45
  • 2
    Аварийная ошибка – Chris 19 May 2015 в 08:40

Вы можете использовать для этого класс android.text.StaticLayout. Это то, что TextView использует внутренне.

1
ответ дан Ted Hopp 21 August 2018 в 04:30
поделиться
  • 1
    Не могли бы вы уточнить? Я вижу dev-doc на StaticLayout, у него есть интересные методы, но я не уверен, что вижу, как это можно использовать для создания того, что я прошу? – Nathan Fig 17 February 2011 в 21:34
  • 2
    Обратитесь к моему комментарию к OP-мне не повезло использовать StaticLayout ... – Nathan Fig 11 March 2011 в 22:05

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

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT YOU WANT TO.
 */

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

/**
 * Text view that auto adjusts text size to fit within the view. If the text
 * size equals the minimum text size and still does not fit, append with an
 * ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 10;

    // Minimum text size for this text view
    public static final float MAX_TEXT_SIZE = 128;

    private static final int BISECTION_LOOP_WATCH_DOG = 30;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for
    // resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = MAX_TEXT_SIZE;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text
     * size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * 
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * 
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * 
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * 
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * 
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * 
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text
     * size
     * 
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if (mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            // mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom()
                    - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }

    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {

        // Height and width with a padding as a percentage of height
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * 
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();

        // Do not resize if the view does not have dimensions or there is no
        // text
        if (text == null || text.length() == 0 || height <= 0 || width <= 0
                || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // Bisection method: fast & precise
        float lower = mMinTextSize;
        float upper = mMaxTextSize;
        int loop_counter = 1;
        float targetTextSize = (lower + upper) / 2;
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        int textWidth = getTextWidth(text, textPaint, width, targetTextSize);

        while (loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
            targetTextSize = (lower + upper) / 2;
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            textWidth = getTextWidth(text, textPaint, width, targetTextSize);
            if (textHeight > (height) || textWidth > (width))
                upper = targetTextSize;
            else
                lower = targetTextSize;
            loop_counter++;
        }

        targetTextSize = lower;
        textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // If we had reached our minimum text size and still don't fit, append
        // an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize
                && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paintCopy = new TextPaint(textPaint);
            paintCopy.setTextSize(targetTextSize);
            StaticLayout layout = new StaticLayout(text, paintCopy, width,
                    Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if (layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut
                // off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if (lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = paintCopy.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the
                    // ellipsis
                    while (width < lineWidth + ellipseWidth) {
                        lineWidth = paintCopy.measureText(text.subSequence(
                                start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line
        // spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint,
            int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paint = new TextPaint(originalPaint);
        // Update the text paint object
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextWidth(CharSequence source, TextPaint originalPaint,
            int width, float textSize) {
        // Update the text paint object
        TextPaint paint = new TextPaint(originalPaint);
        // Draw using a static layout
        paint.setTextSize(textSize);

        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);

        return (int) layout.getLineWidth(0);
    }
}
1
ответ дан Toxid 21 August 2018 в 04:30
поделиться

Надеюсь, это поможет вам

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

/* Based on 
 * from http://stackoverflow.com/questions/2617266/how-to-adjust-text-font-size-to-fit-textview
 */
public class FontFitTextView extends TextView {

private static float MAX_TEXT_SIZE = 20;

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

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

    float size = this.getTextSize();
    if (size > MAX_TEXT_SIZE)
        setTextSize(MAX_TEXT_SIZE);
}

private void refitText(String text, int textWidth) {
    if (textWidth > 0) {
        float availableWidth = textWidth - this.getPaddingLeft()
                - this.getPaddingRight();

        TextPaint tp = getPaint();
        Rect rect = new Rect();
        tp.getTextBounds(text, 0, text.length(), rect);
        float size = rect.width();

        if (size > availableWidth)
            setTextScaleX(availableWidth / size);
    }
}

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

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

ПРИМЕЧАНИЕ. Я использую MAX_TEXT_SIZE, если размер текста больше 20, потому что я не хочу, чтобы большие шрифты применялись к моему представлению, если это не ваш случай, вы можете просто удалить его.

7
ответ дан vsm 21 August 2018 в 04:30
поделиться
  • 1
    Хм - использование "setTextScaleX" просто кажется, что текст становится хрустом горизонтально, вместо того чтобы изменять размер текста до меньшего (еще читаемого) формата. Кроме того, кажется, что текстовое обертывание недоброжелательно. – Nathan Fig 15 March 2011 в 21:17
  • 2
    Этот работал для меня. – Jared Burrows 17 May 2013 в 03:46
  • 3
    не работает для меня – LairdPleng 30 May 2013 в 05:42
  • 4
    Интересное использование побочного эффекта setTextScaleX. Интересно, отличается ли это от изменения размера текста. Возможно, что код один и тот же, но поскольку он является родным, я не вижу источник. – Scott Biggs 24 July 2018 в 21:47
  • 5
    И еще один комментарий, почему тест для scale > availableWidth? Это сделает его изменением размера ТОЛЬКО, если доступный размер меньше, чем первоначально; растяжение (или увеличение текста) не произойдет. – Scott Biggs 24 July 2018 в 22:47

UPDATE: следующий код также удовлетворяет требованию идеального AutoScaleTextView, как описано здесь: Автоматическая подгонка TextView для Android и отмечена как победитель.

ОБНОВЛЕНИЕ 2: Поддержка добавлено maxlines, теперь отлично работает до уровня API 16.

Обновление 3: добавлена ​​поддержка тегов android:drawableLeft, android:drawableRight, android:drawableTop и android:drawableBottom благодаря простому исправлению MartinH здесь .


Мои требования были немного разными. Мне нужен был эффективный способ настройки размера, потому что я анимировал целое число от, может быть от 0 до ~ 4000 в TextView за 2 секунды, и я хотел соответствующим образом настроить размер. Мое решение работает по-другому. Вот как выглядит конечный результат:

enter image description here [/g2]

и код, который его создал:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp" >

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:maxLines="2"
        android:text="Auto Resized Text, max 2 lines"
        android:textSize="100sp" /> <!-- maximum size -->

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:gravity="center"
        android:maxLines="1"
        android:text="Auto Resized Text, max 1 line"
        android:textSize="100sp" /> <!-- maximum size -->

    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Auto Resized Text"
        android:textSize="500sp" /> <!-- maximum size -->

</LinearLayout>

И, наконец, java код:

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;

public class AutoResizeTextView extends TextView {
private interface SizeTester {
    /**
     * 
     * @param suggestedSize
     *            Size of text to be tested
     * @param availableSpace
     *            available space in which text must fit
     * @return an integer < 0 if after applying {@code suggestedSize} to
     *         text, it takes less space than {@code availableSpace}, > 0
     *         otherwise
     */
    public int onTestSize(int suggestedSize, RectF availableSpace);
}

private RectF mTextRect = new RectF();

private RectF mAvailableSpaceRect;

private SparseIntArray mTextCachedSizes;

private TextPaint mPaint;

private float mMaxTextSize;

private float mSpacingMult = 1.0f;

private float mSpacingAdd = 0.0f;

private float mMinTextSize = 20;

private int mWidthLimit;

private static final int NO_LINE_LIMIT = -1;
private int mMaxLines;

private boolean mEnableSizeCache = true;
private boolean mInitiallized;

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

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

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

private void initialize() {
    mPaint = new TextPaint(getPaint());
    mMaxTextSize = getTextSize();
    mAvailableSpaceRect = new RectF();
    mTextCachedSizes = new SparseIntArray();
    if (mMaxLines == 0) {
        // no value was assigned during construction
        mMaxLines = NO_LINE_LIMIT;
    }
    mInitiallized = true;
}

@Override
public void setText(final CharSequence text, BufferType type) {
    super.setText(text, type);
    adjustTextSize(text.toString());
}

@Override
public void setTextSize(float size) {
    mMaxTextSize = size;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

@Override
public void setMaxLines(int maxlines) {
    super.setMaxLines(maxlines);
    mMaxLines = maxlines;
    reAdjust();
}

public int getMaxLines() {
    return mMaxLines;
}

@Override
public void setSingleLine() {
    super.setSingleLine();
    mMaxLines = 1;
    reAdjust();
}

@Override
public void setSingleLine(boolean singleLine) {
    super.setSingleLine(singleLine);
    if (singleLine) {
        mMaxLines = 1;
    } else {
        mMaxLines = NO_LINE_LIMIT;
    }
    reAdjust();
}

@Override
public void setLines(int lines) {
    super.setLines(lines);
    mMaxLines = lines;
    reAdjust();
}

@Override
public void setTextSize(int unit, float size) {
    Context c = getContext();
    Resources r;

    if (c == null)
        r = Resources.getSystem();
    else
        r = c.getResources();
    mMaxTextSize = TypedValue.applyDimension(unit, size,
            r.getDisplayMetrics());
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

@Override
public void setLineSpacing(float add, float mult) {
    super.setLineSpacing(add, mult);
    mSpacingMult = mult;
    mSpacingAdd = add;
}

/**
 * Set the lower text size limit and invalidate the view
 * 
 * @param minTextSize
 */
public void setMinTextSize(float minTextSize) {
    mMinTextSize = minTextSize;
    reAdjust();
}

private void reAdjust() {
    adjustTextSize(getText().toString());
}

private void adjustTextSize(String string) {
    if (!mInitiallized) {
        return;
    }
    int startSize = (int) mMinTextSize;
    int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
        - getCompoundPaddingTop();
    mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
        - getCompoundPaddingRight();
    mAvailableSpaceRect.right = mWidthLimit;
    mAvailableSpaceRect.bottom = heightLimit;
    super.setTextSize(
            TypedValue.COMPLEX_UNIT_PX,
            efficientTextSizeSearch(startSize, (int) mMaxTextSize,
                    mSizeTester, mAvailableSpaceRect));
}

private final SizeTester mSizeTester = new SizeTester() {
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public int onTestSize(int suggestedSize, RectF availableSPace) {
        mPaint.setTextSize(suggestedSize);
        String text = getText().toString();
        boolean singleline = getMaxLines() == 1;
        if (singleline) {
            mTextRect.bottom = mPaint.getFontSpacing();
            mTextRect.right = mPaint.measureText(text);
        } else {
            StaticLayout layout = new StaticLayout(text, mPaint,
                    mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
                    mSpacingAdd, true);
            // return early if we have more lines
            if (getMaxLines() != NO_LINE_LIMIT
                    && layout.getLineCount() > getMaxLines()) {
                return 1;
            }
            mTextRect.bottom = layout.getHeight();
            int maxWidth = -1;
            for (int i = 0; i < layout.getLineCount(); i++) {
                if (maxWidth < layout.getLineWidth(i)) {
                    maxWidth = (int) layout.getLineWidth(i);
                }
            }
            mTextRect.right = maxWidth;
        }

        mTextRect.offsetTo(0, 0);
        if (availableSPace.contains(mTextRect)) {
            // may be too small, don't worry we will find the best match
            return -1;
        } else {
            // too big
            return 1;
        }
    }
};

/**
 * Enables or disables size caching, enabling it will improve performance
 * where you are animating a value inside TextView. This stores the font
 * size against getText().length() Be careful though while enabling it as 0
 * takes more space than 1 on some fonts and so on.
 * 
 * @param enable
 *            enable font size caching
 */
public void enableSizeCache(boolean enable) {
    mEnableSizeCache = enable;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
}

private int efficientTextSizeSearch(int start, int end,
        SizeTester sizeTester, RectF availableSpace) {
    if (!mEnableSizeCache) {
        return binarySearch(start, end, sizeTester, availableSpace);
    }
    String text = getText().toString();
    int key = text == null ? 0 : text.length();
    int size = mTextCachedSizes.get(key);
    if (size != 0) {
        return size;
    }
    size = binarySearch(start, end, sizeTester, availableSpace);
    mTextCachedSizes.put(key, size);
    return size;
}

private static int binarySearch(int start, int end, SizeTester sizeTester,
        RectF availableSpace) {
    int lastBest = start;
    int lo = start;
    int hi = end - 1;
    int mid = 0;
    while (lo <= hi) {
        mid = (lo + hi) >>> 1;
        int midValCmp = sizeTester.onTestSize(mid, availableSpace);
        if (midValCmp < 0) {
            lastBest = lo;
            lo = mid + 1;
        } else if (midValCmp > 0) {
            hi = mid - 1;
            lastBest = hi;
        } else {
            return mid;
        }
    }
    // make sure to return last best
    // this is what should always be returned
    return lastBest;

}

@Override
protected void onTextChanged(final CharSequence text, final int start,
        final int before, final int after) {
    super.onTextChanged(text, start, before, after);
    reAdjust();
}

@Override
protected void onSizeChanged(int width, int height, int oldwidth,
        int oldheight) {
    mTextCachedSizes.clear();
    super.onSizeChanged(width, height, oldwidth, oldheight);
    if (width != oldwidth || height != oldheight) {
        reAdjust();
    }
}
}
136
ответ дан Community 21 August 2018 в 04:31
поделиться
  • 1
    хорошо, я проверил вашу проблему. Вам не нужен этот AutoResizeTextView, проблема в другом. Я прокомментировал ваш вопрос. Эта проблема не имеет ничего общего с размером. – M-WaJeEh 29 August 2013 в 18:40
  • 2
    Я заметил, что есть @TargetApi (Build.VERSION_CODES.JELLY_BEAN). Может ли это работать на 2.3? – Nick Jian 30 September 2013 в 09:29
  • 3
    Рад, что вам понравилось, да, у меня есть лицензионная проверка. [Д0] docs.google.com/document/d/… – M-WaJeEh 13 February 2014 в 08:57
  • 4
    Похоже, что это не будет работать с пользовательскими Typefaces, так как вещи обрезаются вертикально. Как бы вы это сделали? Кто-нибудь пытался с пользовательскими шрифтами? – RealCasually 7 March 2014 в 23:00
  • 5
    @RobinHood Иногда StaticLayout не уважает width. Просто замените последние возвращаемые строки onTestSize() на это «if (availableSpace.contains (mTextRect)) {// может быть слишком маленьким, не волнуйтесь, мы найдем лучший ответ return -1; } else {if (mTextRect.bottom & lt; availableSpace.bottom & amp; mTextRect.right & gt; availableSpace.right) {// hack: O return -1; } // слишком большой возврат 1; }» – M-WaJeEh 24 March 2014 в 18:28
Другие вопросы по тегам:

Похожие вопросы: