Auto-fit TextView для Android
Фон
Много раз нам нужно автоматически подгонять шрифт TextView к заданным границам.
Эта проблема
К сожалению, даже несмотря на то, что существует множество потоков и сообщений (и предлагаемых решений), в которых говорится об этой проблеме (пример здесь, здесь и здесь), ни один из них на самом деле не работает хорошо.
Поэтому я решил проверить каждый из них, пока не найду реальную сделку.
Я думаю, что требования от такого textView должны быть:
-
Должно позволять использовать любой шрифт, шрифт, стиль и набор символов.
-
Должен обрабатывать ширину и высоту
-
Нет усечения, если только текст не может уместиться из-за ограничений, которые мы ему дали (пример: слишком длинный текст, слишком маленький доступный размер). Тем не менее, мы можем запросить горизонтальную/вертикальную полосу прокрутки, если мы хотим, только для этих случаев.
-
Следует разрешить многострочное или однострочное. В случае многострочных, разрешите линии max и min.
-
Не должно быть медленным в вычислениях. Используя петлю для поиска лучшего размера? По крайней мере, оптимизируйте его и не увеличивайте выборку на 1 каждый раз.
-
В случае многострочного, следует разрешить предпочтение изменения размера или использования большего количества строк, и/или позволить самим выбирать строки, используя символ "\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, я решил опубликовать библиотеку, чтобы каждый мог легко использовать ее и внести свой вклад в нее здесь.
Ответы
Ответ 1
Благодаря MartinH простое исправление здесь, этот код также выполняет теги android:drawableLeft
, android:drawableRight
, android:drawableTop
и android:drawableBottom
.
Мой ответ здесь должен сделать вас счастливым Auto Scale TextView Text, чтобы соответствовать в пределах.
Я изменил ваш тестовый пример:
@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), хотя.
Ответ 2
Я немного изменил 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));
}
Ответ 3
Хорошо. Я использовал последнюю неделю для массового переписывания кода, чтобы точно соответствовал вашему тесту. Теперь вы можете скопировать этот 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());
Это даст очень хорошие (и более реалистичные) результаты.
Вы найдете комментарии, чтобы начать работать с этим вопросом.
Удачи и наилучших пожеланий
Ответ 4
Мое требование -
- Нажмите на ScalableTextView
- Откройте listActivity и отобразите различные элементы строки длины.
- Выберите текст из списка.
- Установите текст обратно в ScalableTextView в другое действие.
Я сослался на ссылку: Автомасштабировать TextView Text, чтобы соответствовать в пределах 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);
}
}
}
}
}
Счастливое кодирование....
Ответ 5
Предупреждение, ошибка в 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, но я хотел бы сохранить исправленный код снаружи.
Ответ 6
Сейчас официальное решение этой проблемы. Autosizing TextViews, представленный с Android O, доступен в Библиотеке поддержки 26 и обратно совместим вплоть до Android 4.0.
https://developer.android.com/preview/features/autosizing-textview.html
Я не уверен, почему fooobar.com/questions/12442/..., который также включал эту информацию, был удален администратором.
Ответ 7
Я объясню, как поэтапно работает этот атрибут ниже для версий Android:
1- Импортировать библиотеку поддержки 26.x.x в файл проекта gradle. Если в IDE нет библиотеки поддержки, они будут загружаться автоматически.
dependencies {
compile 'com.android.support:support-v4:26.1.0'
compile 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:support-v13:26.1.0' }
allprojects {
repositories {
jcenter()
maven {
url "https://maven.google.com"
}
} }
2- Откройте XML файл макета и рефакторируйте, как этот тег, TextView. Этот сценарий таков: когда размер шрифта, помещенного в систему, подбирается по доступной ширине, а не к переносу слов.
<android.support.v7.widget.AppCompatTextView
android:id="@+id/textViewAutoSize"
android:layout_width="match_parent"
android:layout_height="25dp"
android:ellipsize="none"
android:text="Auto size text with compatible lower android versions."
android:textSize="12sp"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="4sp"
app:autoSizeStepGranularity="0.5sp"
app:autoSizeTextType="uniform" />
Ответ 8
С июня 2018 года Android официально поддерживает эту функцию для Android 4.0 (уровень API 14) и выше.
С Android 8.0 (уровень API 26) и выше:
setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize,
int autoSizeStepGranularity, int unit);
Версии Android до Android 8.0 (уровень API 26):
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(TextView textView,
int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)
Проверьте мой подробный ответ.
Ответ 9
Преобразование текстового представления в изображение и масштабирование изображения в пределах границ.
Вот пример того, как преобразовать представление в изображение: Преобразование представления в растровое изображение без его отображения на Android?
Проблема в том, что ваш текст не будет выбран, но он должен сделать трюк. Я не пробовал, поэтому я не уверен, как это будет выглядеть (из-за масштабирования).
Ответ 10
Возможно, я последний ответ, но я нашел простое решение, и оно работает так, как я ожидал.
Мое решение: СЕЙЧАС TEXT_VIEW НЕВОЗМОЖНО → УМЕНЬШИТЕ РАЗМЕР, НЕ ДОПУСКАЙТЕ РОДИТЕЛЯ → ТОГДА СДЕЛАЙТЕ ЭТО ВИДИМО.
Требования: TextView имеет значение WRAP_CONTENT, а не margin начало или конец родительского элемента.
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);
}
}
});
}
Используйте этот метод после вызова setText() TextView.
Возможно, это стоит 0,5 секунды для просмотра.
Ответ 11
Ниже представлен avalancha 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". Шрифт должен быть в каталоге ресурсов
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 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 Qaru 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);
}
}
известные ошибки:
Не работает с Android 4.03 - шрифты невидимы или очень малы (оригинальная авария тоже не работает)
ниже обходной путь для этой ошибки: fooobar.com/questions/12442/...
Ответ 12
Попробуйте добавить LayoutParams
и MaxWidth
и MaxHeight
в TextView
. Это заставит макет уважать родительский контейнер, а не переполнять.
textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT));
int GeneralApproxWidthOfContainer = 400;
int GeneralApproxHeightOfContainer = 600;
textview.setMaxWidth(400);
textview.setMaxHeight(600);`
Ответ 13
Попробуйте это
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) { }
};
Ответ 14
Если вы ищете что-то проще:
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);
}
Ответ 15
Быстрое исправление проблемы, описанной @Malachiasz
Я исправил проблему, добавив к ней специальную поддержку в классе автоматического изменения размера:
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;
}
}
Просто позвоните yourView.setTextCompat(newTextValue)
вместо yourView.setText(newTextValue)
Ответ 16
Начиная с Android O, можно автоматически изменять размер текста в 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 позволяет вам указать TextView, чтобы размер текста развернуть или договориться автоматически, чтобы заполнить его макет на основе Характеристики и границы TextView. Этот параметр делает это проще оптимизировать размер текста на разных экранах с динамическим содержание.
Библиотека поддержки 26.0 Beta обеспечивает полную поддержку автосохранения Функция TextView на устройствах под управлением Android версий до Android О. Библиотека поддерживает Android 4.0 (уровень API 14) и выше. Пакет android.support.v4.widget содержит Класс TextViewCompat для доступа к функциям в обратной совместимости мода.
Ответ 17
После того, как я попробовал официальный Autosizing TextView для Android, я обнаружил, что ваша версия Android до Android 8.0 (уровень API 26), вам нужно использовать android.support.v7.widget.AppCompatTextView
и убедиться, что ваша версия библиотеки поддержки выше 26.0.0, Пример:
<android.support.v7.widget.AppCompatTextView
android:layout_width="130dp"
android:layout_height="32dp"
android:maxLines="1"
app:autoSizeMaxTextSize="22sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="2sp"
app:autoSizeTextType="uniform" />