Auto-fit TextView для Android

Задний план

Много раз нам нужно автоподгонку шрифта TextView к указанным им границам.

Проблема

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

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

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

  1. Должен разрешить использование любого шрифта, шрифта, стиля и набора символов.

  2. Должен обрабатывать как ширину, так и высоту

  3. Нет усечения, если текст не может соответствовать из-за ограничения, которое мы ему дали (пример: слишком длинный текст, слишком маленький доступный размер). Однако мы можем запросить горизонтальную / вертикальную полосу прокрутки, если хотим, только для этих случаев.

  4. Должен разрешить многострочный или однострочный. В случае многострочной линии, разрешите max & min lines.

  5. Не должно быть медленным в вычислении. Используя петлю для поиска лучшего размера? По крайней мере, оптимизируйте его и не увеличивайте выборку на 1 каждый раз.

  6. В случае многострочной линии следует разрешить изменять размер или использовать больше строк и / или разрешить сами выбирать строки, используя символ «\ n».

Что я пробовал

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

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

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

Вот код (также доступен здесь ):

Файл res/layout/activity_main.xml

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:text="Button" /> <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/button1" android:layout_alignParentLeft="true" android:background="#ffff0000" android:layout_alignParentRight="true" android:id="@+id/container" android:layout_alignParentTop="true" /> </RelativeLayout> 

Файл src/.../MainActivity.java

 public class MainActivity extends Activity { private final Random _random =new Random(); private static final String ALLOWED_CHARACTERS ="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ViewGroup container=(ViewGroup)findViewById(R.id.container); findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { container.removeAllViews(); final int maxWidth=container.getWidth(); final int maxHeight=container.getHeight(); final FontFitTextView fontFitTextView=new FontFitTextView(MainActivity.this); final int width=_random.nextInt(maxWidth)+1; final int height=_random.nextInt(maxHeight)+1; fontFitTextView.setLayoutParams(new LayoutParams(width,height)); fontFitTextView.setSingleLine(); fontFitTextView.setBackgroundColor(0xff00ff00); final String text=getRandomText(); fontFitTextView.setText(text); container.addView(fontFitTextView); Log.d("DEBUG","width:"+width+" height:"+height+" text:"+text); } }); } private String getRandomText() { final int textLength=_random.nextInt(20)+1; final StringBuilder builder=new StringBuilder(); for(int i=0;i<textLength;++i) builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length()))); return builder.toString(); } } 

Вопрос

Кто-нибудь знает о решении этой общей проблемы, которая действительно работает?

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


Проект GitHub

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

Solutions Collecting From Web of "Auto-fit TextView для Android"

Благодаря простому исправлению MartinH, этот код также поддерживает android:drawableLeft , android:drawableRight , android:drawableTop и android:drawableBottom .


Мой ответ здесь должен сделать вас счастливым. Auto Scale TextView Text to Fit in Bounds

Я изменил ваш тестовый пример:

 @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ViewGroup container = (ViewGroup) findViewById(R.id.container); findViewById(R.id.button1).setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { container.removeAllViews(); final int maxWidth = container.getWidth(); final int maxHeight = container.getHeight(); final AutoResizeTextView fontFitTextView = new AutoResizeTextView(MainActivity.this); final int width = _random.nextInt(maxWidth) + 1; final int height = _random.nextInt(maxHeight) + 1; fontFitTextView.setLayoutParams(new FrameLayout.LayoutParams( width, height)); int maxLines = _random.nextInt(4) + 1; fontFitTextView.setMaxLines(maxLines); fontFitTextView.setTextSize(500);// max size fontFitTextView.enableSizeCache(false); fontFitTextView.setBackgroundColor(0xff00ff00); final String text = getRandomText(); fontFitTextView.setText(text); container.addView(fontFitTextView); Log.d("DEBUG", "width:" + width + " height:" + height + " text:" + text + " maxLines:" + maxLines); } }); } 

Я размещаю код здесь по запросу разработчика Android :

Конечный эффект:

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

Пример файла макета:

 <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 mInitializedDimens; 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; } } @Override public void setTextSize(float size) { mMaxTextSize = size; mTextCachedSizes.clear(); adjustTextSize(); } @Override public void setMaxLines(int maxlines) { super.setMaxLines(maxlines); mMaxLines = maxlines; adjustTextSize(); } public int getMaxLines() { return mMaxLines; } @Override public void setSingleLine() { super.setSingleLine(); mMaxLines = 1; adjustTextSize(); } @Override public void setSingleLine(boolean singleLine) { super.setSingleLine(singleLine); if (singleLine) { mMaxLines = 1; } else { mMaxLines = NO_LINE_LIMIT; } adjustTextSize(); } @Override public void setLines(int lines) { super.setLines(lines); mMaxLines = lines; adjustTextSize(); } @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(); } @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; adjustTextSize(); } private void adjustTextSize() { if (!mInitializedDimens) { 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); } int key = getText().toString().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 the 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); adjustTextSize(); } @Override protected void onSizeChanged(int width, int height, int oldwidth, int oldheight) { mInitializedDimens = true; mTextCachedSizes.clear(); super.onSizeChanged(width, height, oldwidth, oldheight); if (width != oldwidth || height != oldheight) { adjustTextSize(); } } } 

Предупреждение:

Опасайтесь этой разрешенной ошибки в Android 3.1 (Honeycomb).

Я немного изменил ответ M-WaJeEh, чтобы учесть составные чертежи по сторонам.

getCompoundPaddingXXXX() возвращают padding of the view + drawable space . См. Например: TextView.getCompoundPaddingLeft ()

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


Обновлен сегмент adjustTextSize(String) :

 private void adjustTextSize(final String text) { if (!mInitialized) { return; } int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop(); mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(); mAvailableSpaceRect.right = mWidthLimit; mAvailableSpaceRect.bottom = heightLimit; int maxTextSplits = text.split(" ").length; AutoResizeTextView.super.setMaxLines(Math.min(maxTextSplits, mMaxLines)); super.setTextSize( TypedValue.COMPLEX_UNIT_PX, binarySearch((int) mMinTextSize, (int) mMaxTextSize, mSizeTester, mAvailableSpaceRect)); } 

Хорошо, что я использовал последнюю неделю для массового переписывания кода, чтобы точно соответствовать вашему тесту. Теперь вы можете скопировать этот 1: 1, и он сразу же будет работать, включая setSingleLine() . Не забудьте настроить MIN_TEXT_SIZE и MAX_TEXT_SIZE если вы собираетесь использовать экстремальные значения.

Конвергентный алгоритм выглядит так:

 for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) { // Go to the mean value... testSize = (upperTextSize + lowerTextSize) / 2; // ... inflate the dummy TextView by setting a scaled textSize and the text... mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor); mTestView.setText(text); // ... call measure to find the current values that the text WANTS to occupy mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); int tempHeight = mTestView.getMeasuredHeight(); // ... decide whether those values are appropriate. if (tempHeight >= targetFieldHeight) { upperTextSize = testSize; // Font is too big, decrease upperSize } else { lowerTextSize = testSize; // Font is too small, increase lowerSize } } 

И весь класс можно найти здесь.

Результат теперь очень гибкий. Это работает так же, как и в xml:

 <com.example.myProject.AutoFitText android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="4" android:text="@string/LoremIpsum" /> 

… а также построены программно, как в вашем тесте.

Я действительно надеюсь, что вы сможете использовать это сейчас. Вы можете позвонить setText(CharSequence text) чтобы использовать его кстати. Класс заботится о необычайно редких исключениях и должен быть прочным. Единственное, что алгоритм еще не поддерживает:

  • setMaxLines(x) где x >= 2

Но я добавил обширные комментарии, чтобы помочь вам построить это, если хотите!


Пожалуйста, обратите внимание:

Если вы просто используете это нормально, не ограничивая его одной строкой, тогда, как вы упомянули ранее, может быть словосочетание. Это функция Android , а не ошибка AutoFitText . Android всегда будет сломать слова, которые слишком длинны для TextView, и на самом деле это довольно удобно. Если вы хотите вмешаться здесь, пожалуйста, ознакомьтесь с моими комментариями и кодом, начиная с строки 203. Я уже написал адекватный раскол и признание для вас, все, что вам нужно будет делать впредь, состоит в том, чтобы разделить слова, а затем изменить, как вы пожелаете ,

В заключение: вы должны очень серьезно подумать о том, чтобы переписать свой тест, чтобы также поддерживать символы пространства, например:

 final Random _random = new Random(); final String ALLOWED_CHARACTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; final int textLength = _random.nextInt(80) + 20; final StringBuilder builder = new StringBuilder(); for (int i = 0; i < textLength; ++i) { if (i % 7 == 0 && i != 0) { builder.append(" "); } builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length()))); } ((AutoFitText) findViewById(R.id.textViewMessage)).setText(builder.toString()); 

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

Удачи и наилучших пожеланий

Мое требование –

  • Нажмите на ScalableTextView
  • Откройте listActivity и отобразите различные элементы строки длины.
  • Выберите текст из списка.
  • Установите текст обратно в ScalableTextView в другое действие.

Я ссылался на ссылку: Auto Scale TextView Text to Fit Within Bounds (включая комментарии), а также DialogTitle.java

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

Я изменил ScalableTextView.java, чтобы скорректировать размер текста на основе длины текста. Вот мой ScalableTextView.java

 public class ScalableTextView extends TextView { float defaultTextSize = 0.0f; public ScalableTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setSingleLine(); setEllipsize(TruncateAt.END); defaultTextSize = getTextSize(); } public ScalableTextView(Context context, AttributeSet attrs) { super(context, attrs); setSingleLine(); setEllipsize(TruncateAt.END); defaultTextSize = getTextSize(); } public ScalableTextView(Context context) { super(context); setSingleLine(); setEllipsize(TruncateAt.END); defaultTextSize = getTextSize(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize); super.onMeasure(widthMeasureSpec, heightMeasureSpec); final Layout layout = getLayout(); if (layout != null) { final int lineCount = layout.getLineCount(); if (lineCount > 0) { int ellipsisCount = layout.getEllipsisCount(lineCount - 1); while (ellipsisCount > 0) { final float textSize = getTextSize(); // textSize is already expressed in pixels setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1)); super.onMeasure(widthMeasureSpec, heightMeasureSpec); ellipsisCount = layout.getEllipsisCount(lineCount - 1); } } } } } 

Счастливое кодирование ….

Предупреждение, ошибка в Android 3 (Honeycomb) и Android 4.0 (сэндвич с мороженым)

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

Ошибка описана в выпуске 22493: ошибка высоты TextView в Android 4.0 и проблема 17343: высота и текст кнопки не возвращаются в исходное состояние после увеличения и уменьшения размера текста на HoneyComb .

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

Преобразование текстового представления в изображение и масштабирование изображения в пределах границ.

Вот пример того, как преобразовать представление в изображение: преобразование представления в растровое изображение без отображения его в Android?

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

В настоящее время официальное решение этой проблемы. Autosizing TextViews, представленные в Android O, доступны в Библиотеке поддержки 26 и полностью совместимы с Android 4.0.

https://developer.android.com/preview/features/autosizing-textview.html

Я не знаю, почему https://stackoverflow.com/a/42940171/47680, который также включил эту информацию, был удален администратором.

Ниже представлен лавинный текст TextView с добавленной функциональностью для пользовательского шрифта.

Применение:

 <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:foo="http://schemas.android.com/apk/res-auto" android:layout_width="wrap_content" android:layout_height="match_parent" > <de.meinprospekt.androidhd.view.AutoFitText android:layout_width="wrap_content" android:layout_height="10dp" android:text="Small Text" android:textColor="#FFFFFF" android:textSize="100sp" foo:customFont="fonts/Roboto-Light.ttf" /> </FrameLayout> 

Не забудьте добавить: xmlns: foo = "http://schemas.android.com/apk/res-auto&quot;. Шрифт должен быть в директории активов

 import java.util.ArrayList; import java.util.List; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Paint; import android.graphics.Typeface; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.TextView; import de.meinprospekt.androidhd.R; import de.meinprospekt.androidhd.adapter.BrochuresHorizontalAdapter; import de.meinprospekt.androidhd.util.LOG; /** * https://stackoverflow.com/a/16174468/2075875 This class builds a new android Widget named AutoFitText which can be used instead of a TextView to * have the text font size in it automatically fit to match the screen width. Credits go largely to Dunni, gjpc, gregm and speedplane from * Stackoverflow, method has been (style-) optimized and rewritten to match android coding standards and our MBC. This version upgrades the original * "AutoFitTextView" to now also be adaptable to height and to accept the different TextView types (Button, TextClock etc.) * * @author pheuschk * @createDate: 18.04.2013 * * combined with: https://stackoverflow.com/a/7197867/2075875 */ @SuppressWarnings("unused") public class AutoFitText extends TextView { private static final String TAG = AutoFitText.class.getSimpleName(); /** Global min and max for text size. Remember: values are in pixels! */ private final int MIN_TEXT_SIZE = 10; private final int MAX_TEXT_SIZE = 400; /** Flag for singleLine */ private boolean mSingleLine = false; /** * A dummy {@link TextView} to test the text size without actually showing anything to the user */ private TextView mTestView; /** * A dummy {@link Paint} to test the text size without actually showing anything to the user */ private Paint mTestPaint; /** * Scaling factor for fonts. It's a method of calculating independently (!) from the actual density of the screen that is used so users have the * same experience on different devices. We will use DisplayMetrics in the Constructor to get the value of the factor and then calculate SP from * pixel values */ private float mScaledDensityFactor; /** * Defines how close we want to be to the factual size of the Text-field. Lower values mean higher precision but also exponentially higher * computing cost (more loop runs) */ private final float mThreshold = 0.5f; /** * Constructor for call without attributes --> invoke constructor with AttributeSet null * * @param context */ public AutoFitText(Context context) { this(context, null); } public AutoFitText(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public AutoFitText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context, attrs); } private void init(Context context, AttributeSet attrs) { //TextViewPlus part https://stackoverflow.com/a/7197867/2075875 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoFitText); String customFont = a.getString(R.styleable.AutoFitText_customFont); setCustomFont(context, customFont); a.recycle(); // AutoFitText part mScaledDensityFactor = context.getResources().getDisplayMetrics().scaledDensity; mTestView = new TextView(context); mTestPaint = new Paint(); mTestPaint.set(this.getPaint()); this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // make an initial call to onSizeChanged to make sure that refitText is triggered onSizeChanged(AutoFitText.this.getWidth(), AutoFitText.this.getHeight(), 0, 0); // Remove the LayoutListener immediately so we don't run into an infinite loop //AutoFitText.this.getViewTreeObserver().removeOnGlobalLayoutListener(this); removeOnGlobalLayoutListener(AutoFitText.this, this); } }); } public boolean setCustomFont(Context ctx, String asset) { Typeface tf = null; try { tf = Typeface.createFromAsset(ctx.getAssets(), asset); } catch (Exception e) { LOG.e(TAG, "Could not get typeface: "+e.getMessage()); return false; } setTypeface(tf); return true; } @SuppressLint("NewApi") public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){ if (Build.VERSION.SDK_INT < 16) { v.getViewTreeObserver().removeGlobalOnLayoutListener(listener); } else { v.getViewTreeObserver().removeOnGlobalLayoutListener(listener); } } /** * Main method of this widget. Resizes the font so the specified text fits in the text box assuming the text box has the specified width. This is * done via a dummy text view that is refit until it matches the real target width and height up to a certain threshold factor * * @param targetFieldWidth The width that the TextView currently has and wants filled * @param targetFieldHeight The width that the TextView currently has and wants filled */ private void refitText(String text, int targetFieldWidth, int targetFieldHeight) { // Variables need to be visible outside the loops for later use. Remember size is in pixels float lowerTextSize = MIN_TEXT_SIZE; float upperTextSize = MAX_TEXT_SIZE; // Force the text to wrap. In principle this is not necessary since the dummy TextView // already does this for us but in rare cases adding this line can prevent flickering this.setMaxWidth(targetFieldWidth); // Padding should not be an issue since we never define it programmatically in this app // but just to to be sure we cut it off here targetFieldWidth = targetFieldWidth - this.getPaddingLeft() - this.getPaddingRight(); targetFieldHeight = targetFieldHeight - this.getPaddingTop() - this.getPaddingBottom(); // Initialize the dummy with some params (that are largely ignored anyway, but this is // mandatory to not get a NullPointerException) mTestView.setLayoutParams(new LayoutParams(targetFieldWidth, targetFieldHeight)); // maxWidth is crucial! Otherwise the text would never line wrap but blow up the width mTestView.setMaxWidth(targetFieldWidth); if (mSingleLine) { // the user requested a single line. This is very easy to do since we primarily need to // respect the width, don't have to break, don't have to measure... /*************************** Converging algorithm 1 ***********************************/ for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) { // Go to the mean value... testSize = (upperTextSize + lowerTextSize) / 2; mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor); mTestView.setText(text); mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); if (mTestView.getMeasuredWidth() >= targetFieldWidth) { upperTextSize = testSize; // Font is too big, decrease upperSize } else { lowerTextSize = testSize; // Font is too small, increase lowerSize } } /**************************************************************************************/ // In rare cases with very little letters and width > height we have vertical overlap! mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); if (mTestView.getMeasuredHeight() > targetFieldHeight) { upperTextSize = lowerTextSize; lowerTextSize = MIN_TEXT_SIZE; /*************************** Converging algorithm 1.5 *****************************/ for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) { // Go to the mean value... testSize = (upperTextSize + lowerTextSize) / 2; mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor); mTestView.setText(text); mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); if (mTestView.getMeasuredHeight() >= targetFieldHeight) { upperTextSize = testSize; // Font is too big, decrease upperSize } else { lowerTextSize = testSize; // Font is too small, increase lowerSize } } /**********************************************************************************/ } } else { /*********************** Converging algorithm 2 ***************************************/ // Upper and lower size converge over time. As soon as they're close enough the loop // stops // TODO probe the algorithm for cost (ATM possibly O(n^2)) and optimize if possible for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) { // Go to the mean value... testSize = (upperTextSize + lowerTextSize) / 2; // ... inflate the dummy TextView by setting a scaled textSize and the text... mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor); mTestView.setText(text); // ... call measure to find the current values that the text WANTS to occupy mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); int tempHeight = mTestView.getMeasuredHeight(); // int tempWidth = mTestView.getMeasuredWidth(); // LOG.debug("Measured: " + tempWidth + "x" + tempHeight); // LOG.debug("TextSize: " + testSize / mScaledDensityFactor); // ... decide whether those values are appropriate. if (tempHeight >= targetFieldHeight) { upperTextSize = testSize; // Font is too big, decrease upperSize } else { lowerTextSize = testSize; // Font is too small, increase lowerSize } } /**************************************************************************************/ // It is possible that a single word is wider than the box. The Android system would // wrap this for us. But if you want to decide fo yourself where exactly to break or to // add a hyphen or something than you're going to want to implement something like this: mTestPaint.setTextSize(lowerTextSize); List<String> words = new ArrayList<String>(); for (String s : text.split(" ")) { Log.i("tag", "Word: " + s); words.add(s); } for (String word : words) { if (mTestPaint.measureText(word) >= targetFieldWidth) { List<String> pieces = new ArrayList<String>(); // pieces = breakWord(word, mTestPaint.measureText(word), targetFieldWidth); // Add code to handle the pieces here... } } } /** * We are now at most the value of threshold away from the actual size. To rather undershoot than overshoot use the lower value. To match * different screens convert to SP first. See {@link http://developer.android.com/guide/topics/resources/more-resources.html#Dimension} for * more details */ this.setTextSize(TypedValue.COMPLEX_UNIT_SP, lowerTextSize / mScaledDensityFactor); return; } /** * This method receives a call upon a change in text content of the TextView. Unfortunately it is also called - among others - upon text size * change which means that we MUST NEVER CALL {@link #refitText(String)} from this method! Doing so would result in an endless loop that would * ultimately result in a stack overflow and termination of the application * * So for the time being this method does absolutely nothing. If you want to notify the view of a changed text call {@link #setText(CharSequence)} */ @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { // Super implementation is also intentionally empty so for now we do absolutely nothing here super.onTextChanged(text, start, lengthBefore, lengthAfter); } @Override protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { if (width != oldWidth && height != oldHeight) { refitText(this.getText().toString(), width, height); } } /** * This method is guaranteed to be called by {@link TextView#setText(CharSequence)} immediately. Therefore we can safely add our modifications * here and then have the parent class resume its work. So if text has changed you should always call {@link TextView#setText(CharSequence)} or * {@link TextView#setText(CharSequence, BufferType)} if you know whether the {@link BufferType} is normal, editable or spannable. Note: the * method will default to {@link BufferType#NORMAL} if you don't pass an argument. */ @Override public void setText(CharSequence text, BufferType type) { int targetFieldWidth = this.getWidth(); int targetFieldHeight = this.getHeight(); if (targetFieldWidth <= 0 || targetFieldHeight <= 0 || text.equals("")) { // Log.v("tag", "Some values are empty, AutoFitText was not able to construct properly"); } else { refitText(text.toString(), targetFieldWidth, targetFieldHeight); } super.setText(text, type); } /** * TODO add sensibility for {@link #setMaxLines(int)} invocations */ @Override public void setMaxLines(int maxLines) { // TODO Implement support for this. This could be relatively easy. The idea would probably // be to manipulate the targetHeight in the refitText-method and then have the algorithm do // its job business as usual. Nonetheless, remember the height will have to be lowered // dynamically as the font size shrinks so it won't be a walk in the park still if (maxLines == 1) { this.setSingleLine(true); } else { throw new UnsupportedOperationException("MaxLines != 1 are not implemented in AutoFitText yet, use TextView instead"); } } @Override public void setSingleLine(boolean singleLine) { // save the requested value in an instance variable to be able to decide later mSingleLine = singleLine; super.setSingleLine(singleLine); } } 

known bugs: Doesn't work with Android 4.03 – fonts are invisible or very small (original avalancha doesn't work too) below is workaround for that bug: https://stackoverflow.com/a/21851239/2075875

Попробуй это

 TextWatcher changeText = new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { tv3.setText(et.getText().toString()); tv3.post(new Runnable() { @Override public void run() { while(tv3.getLineCount() >= 3){ tv3.setTextSize((tv3.getTextSize())-1); } } }); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void afterTextChanged(Editable s) { } }; 

If you are looking for something easier:

  public MyTextView extends TextView{ public void resize(String text, float textViewWidth, float textViewHeight) { Paint p = new Paint(); Rect bounds = new Rect(); p.setTextSize(1); p.getTextBounds(text, 0, text.length(), bounds); float widthDifference = (textViewWidth)/bounds.width(); float heightDifference = (textViewHeight); textSize = Math.min(widthDifference, heightDifference); setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); } 

Quick fix for the issue described by @Malachiasz

I've fixed the issue by adding custom support for this in the auto resize class:

 public void setTextCompat(final CharSequence text) { setTextCompat(text, BufferType.NORMAL); } public void setTextCompat(final CharSequence text, BufferType type) { // Quick fix for Android Honeycomb and Ice Cream Sandwich which sets the text only on the first call if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { super.setText(DOUBLE_BYTE_WORDJOINER + text + DOUBLE_BYTE_WORDJOINER, type); } else { super.setText(text, type); } } @Override public CharSequence getText() { String originalText = super.getText().toString(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) { // We try to remove the word joiners we added using compat method - if none found - this will do nothing. return originalText.replaceAll(DOUBLE_BYTE_WORDJOINER, ""); } else { return originalText; } } 

Just call yourView.setTextCompat(newTextValue) instead of yourView.setText(newTextValue)

Maybe I'm the last one answer here but I found a simple solution and it works as I expected.

My solution: MAKE TEXT_VIEW INVISIBLE -> REDUCE SIZE UNTIL FIT PARENT -> THEN MAKE IT VISIBLE.

Requirements: The TextView is WRAP_CONTENT width, not margin start or end to parent.

 public void autoResizeTextView(final TextView tvContent, final View parent, final float originalSize, final int maxLine) { tvContent.setVisibility(View.INVISIBLE); tvContent.getViewTreeObserver() .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { private float mOriginalSize = originalSize; private float mParentWidth = 0f; @Override public void onGlobalLayout() { // get parent width just only one time. if (mParentWidth == 0f) { mParentWidth = parent.getMeasuredWidth(); } if (tvContent.getMeasuredWidth() == mParentWidth && tvContent.getLineCount() > maxLine) { mOriginalSize -= 0.5f; tvContent.setTextSize(mOriginalSize); } else { tvContent.setVisibility(View.VISIBLE); tvContent.getViewTreeObserver() .removeOnGlobalLayoutListener(this); } } }); } 

Use this method after calling setText() of TextView. Maybe it costs 0.5 second to view.

Try adding LayoutParams and MaxWidth and MaxHeight to the TextView . It will force the layout to respect the parent container and not overflow.

 textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT)); int GeneralApproxWidthOfContainer = 400; int GeneralApproxHeightOfContainer = 600; textview.setMaxWidth(400); textview.setMaxHeight(600);` 

Since Android O, it's possible to auto resize text in xml:

https://developer.android.com/preview/features/autosizing-textview.html

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

Android O allows you to instruct a TextView to let the text size expand or contract automatically to fill its layout based on the TextView's characteristics and boundaries. This setting makes it easier to optimize the text size on different screens with dynamic content.

The Support Library 26.0 Beta provides full support to the autosizing TextView feature on devices running Android versions prior to Android O. The library provides support to Android 4.0 (API level 14) and higher. The android.support.v4.widget package contains the TextViewCompat class to access features in a backward-compatible fashion.