自动缩放TextView文本以适应边界

我正在寻找一种最佳的方法来调整TextView中的换行文本的大小,以便它适合它的getHeight和getWidth边界。我不仅仅是在寻找一种包装文本的方法-我想确保它既能包装又足够小,可以完全适合屏幕。

我在StackOverflow上看到过一些需要自动调整大小的情况,但它们要么是黑客解决方案的非常特殊的情况,没有解决方案,要么涉及递归重新绘制TextView,直到它足够小(这是内存密集的,并迫使用户在每次递归中逐步缩小文本)。

但我相信有人已经找到了一个好的解决方案,而不涉及我正在做的事情:编写几个繁重的例程来解析和测量文本,调整文本大小,并重复直到找到合适的小尺寸。

TextView使用什么例程来包装文本?这些例程不能以某种方式用于预测文本是否足够小吗?

tl; dr:是否有一种最佳实践方法来自动调整TextView的大小以适应其getHeight和getWidth边界?

526354 次浏览

您可以为此使用android.text.StaticLayout类。这就是TextView在内部使用的。

以下是我为仍在搜索的人找到的其他内容的枚举:

1)这里有一个解决方案,递归地重新绘制文本视图,直到它适合。这意味着从字面上看你的文本收缩到位,但至少完成后它适合。代码需要一些调整来实现,但它大部分都在那里。

2)您可以尝试将这个这个中的Dunni类之类的自定义解决方案组合在一起,这就是我使用getP的所做的。测量文本(str)来搜索合适的大小,但是它变得更加混乱,因为我只需要它在空格上包装…

3)你可以继续搜索——我已经尝试了更多的替代方案。Ted关于静态布局的建议没有给我带来回报,但也许那里有一些东西;我试着使用StaticLayout.get省略号(行)来确定文本是否离开屏幕,没有效果。见我(目前未回复)关于那个这里的帖子。

我希望这对你有帮助

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);}}
@Overrideprotected 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);}
@Overrideprotected void onTextChanged(final CharSequence text, final int start,final int before, final int after) {refitText(text.toString(), this.getWidth());}
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {if (w != oldw) {refitText(this.getText().toString(), w);}}}

注意:我在文本大小大于20的情况下使用MAX_TEXT_SIZE,因为我不想让大字体应用于我的视图,如果这不是你的情况,你可以简单地删除它。

我写了一篇关于这个的博客文章。

我基于Kirill Grouchnikov关于新android市场应用程序中使用的自定义组件的博客文章创建了一个名为ResizableButton的组件。我放置了src代码这里

另一方面,mosabua阅读了我的帖子并告诉我他将开源他的实现,它比我的更快。我希望他能尽快发布它:)

作为一名移动开发人员,我很难过没有找到任何支持自动调整大小的原生内容。我的搜索没有找到任何对我有用的东西,最后,我花了我周末的一半时间创建了自己的自动调整大小文本视图。我将在这里发布代码,希望它对其他人有用。

这个类使用带有原始文本视图的文本绘制的静态布局来测量高度。从那里,我降低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 viewpublic static final float MIN_TEXT_SIZE = 20;
// Interface for resize notificationspublic interface OnTextResizeListener {public void onTextResize(TextView textView, float oldSize, float newSize);}
// Our ellipse stringprivate static final String mEllipsis = "...";
// Registered resize listenerprivate OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resizeprivate boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizingprivate float mTextSize;
// Temporary upper bounds on the starting text sizeprivate float mMaxTextSize = 0;
// Lower bounds for text sizeprivate float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplierprivate float mSpacingMult = 1.0f;
// Text view additional line spacingprivate float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text sizeprivate boolean mAddEllipsis = true;
// Default constructor overridepublic AutoResizeTextView(Context context) {this(context, null);}
// Default constructor when inflating from XML filepublic AutoResizeTextView(Context context, AttributeSet attrs) {this(context, attrs, 0);}
// Default constructor overridepublic 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.*/@Overrideprotected 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 sizeresetTextSize();}
/*** If the text view size changed, set the force resize flag to true*/@Overrideprotected 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*/@Overridepublic void setTextSize(float size) {super.setTextSize(size);mTextSize = getTextSize();}
/*** Override the set text size to update our internal reference values*/@Overridepublic void setTextSize(int unit, float size) {super.setTextSize(unit, size);mTextSize = getTextSize();}
/*** Override the set line spacing to update our internal reference values*/@Overridepublic 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*/@Overrideprotected 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 textif (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 objectTextPaint textPaint = getPaint();
// Store the current text sizefloat oldTextSize = textPaint.getTextSize();// If there is a max text size set, use the lesser of that and the default text sizefloat targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text heightint 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 sizeswhile (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 ellipsisif (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {// Draw using a static layout// modified: use a copy of TextPaint for measuringTextPaint paint = new TextPaint(textPaint);// Draw using a static layoutStaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);// Check that we have a least one line of rendered textif (layout.getLineCount() > 0) {// Since the line at the specific vertical position would be cut off,// we must trim up to the previous lineint lastLine = layout.getLineForVertical(height) - 1;// If the text would not even fit on a single line, clear itif (lastLine < 0) {setText("");}// Otherwise, trim to the previous line and add an ellipsiselse {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 ellipsiswhile (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 effectsetTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registeredif (mTextResizeListener != null) {mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);}
// Reset force resize flagmNeedsResize = false;}
// Set the text size of the text paint object and use a static layout to render text off screen before measuringprivate 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 objectpaintCopy.setTextSize(textSize);// Measure using a static layoutStaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);return layout.getHeight();}
}

警告。有一个重要的固定bug影响Android 3.1-4.04导致所有AutoResizingTextView小部件无法工作。请阅读:https://stackoverflow.com/a/21851157/2075875

这些解决方案适用于我们:

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();}}}
@Overrideprotected 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);}
@Overrideprotected 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());}
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh){super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw)refitText(this.getText().toString(), w);}}

我发现以下内容对我很有效。它不循环并同时考虑高度和宽度。请注意,在视图上调用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 resultwidth = paint.measureText(text10)*numCharacters/text10.length();newSize = (int)((widthPixels/width)*paint.getTextSize());paint.setTextSize(newSize);
// Check height constraintsFontMetricsInt metrics = paint.getFontMetricsInt();float textHeight = metrics.descent-metrics.ascent;if (textHeight > heightPixels) {newSize = (int)(newSize * (heightPixels/textHeight));paint.setTextSize(newSize);}
return paint;}

我从Chase的AutoResizeTextView类开始,并做了一个小改动,使其既适合垂直又适合水平。

我还发现了一个bug,它在一些相当模糊的条件下导致布局编辑器(在Eclipse中)中的空指针异常。

变化1:垂直和水平都适合文本

Chase的原始版本减少了文本大小,直到它垂直适合,但允许文本比目标更宽。在我的情况下,我需要文本适应指定的宽度。

此更改使其调整大小,直到文本垂直和水平都适合。

resizeText(int,int)中更改为:

// Get the required text heightint 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 sizeswhile(textHeight > height && targetTextSize > mMinTextSize) {targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);textHeight = getTextHeight(text, textPaint, width, targetTextSize);}

到:

// Get the required text heightint 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 sizeswhile(((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 measuringprivate int getTextWidth(CharSequence source, TextPaint paint, int width, float textSize) {// Update the text paint objectpaint.setTextSize(textSize);// Draw using a static layoutStaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);layout.draw(sTextResizeCanvas);return layout.getWidth();}




更改2:修复Eclipse Android布局编辑器中的空StackException

在相当模糊和非常精确的条件下,布局编辑器将无法显示布局的图形显示;它将在com.android.ide.eclipse.adt.中抛出一个“空StackException: null”异常

需要的条件是:
-创建AutoResizeTextView小部件
-为该小部件创建样式
-在样式中指定文本项;不在小部件定义中

如:

res/布局/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.AutoResizeTextViewandroid:id="@+id/resizingText"style="@style/myTextStyle" />
</LinearLayout>

res/值/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时选择图形布局选项卡将显示:

出错!
返回值:null
异常详细信息记录在窗口>显示视图>错误日志

而不是布局的图形视图。

为了缩短已经太长的故事,我将其追踪到以下几行(再次在resizeText中):

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

问题是在特定条件下,mTextSize永远不会初始化;它的值为0。

在上面,targetTextSize被设置为零(作为Math.min的结果)。

该零作为textSize参数传递给getTextHeight()(和getTextWidth())。当它到达
layout.draw(sTextResizeCanvas);
我们得到了例外。

resizeText()的开头测试(mTextSize == 0)比在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 viewpublic static final float MIN_TEXT_SIZE = 20;
// Interface for resize notificationspublic interface OnTextResizeListener {public void onTextResize(TextView textView, float oldSize, float newSize);}
// Off screen canvas for text size renderingprivate static final Canvas sTextResizeCanvas = new Canvas();
// Our ellipse stringprivate static final String mEllipsis = "...";
// Registered resize listenerprivate OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resizeprivate boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for// resizingprivate float mTextSize;
// Temporary upper bounds on the starting text sizeprivate float mMaxTextSize = 0;
// Lower bounds for text sizeprivate float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplierprivate float mSpacingMult = 1.0f;
// Text view additional line spacingprivate float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text sizeprivate boolean mAddEllipsis = true;

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

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

// Default constructor overridepublic 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.*/@Overrideprotected 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 sizeresetTextSize();}

/*** If the text view size changed, set the force resize flag to true*/@Overrideprotected 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*/@Overridepublic void setTextSize(float size) {super.setTextSize(size);mTextSize = getTextSize();}

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

/*** Override the set line spacing to update our internal reference values*/@Overridepublic 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*/@Overrideprotected 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 initializedif (text == null || text.length() == 0 || height <= 0 || width <= 0|| mTextSize == 0) {return;}
// Get the text view's paint objectTextPaint textPaint = getPaint();
// Store the current text sizefloat oldTextSize = textPaint.getTextSize();
// If there is a max text size set, use the lesser of that and the// default text sizefloat targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize): mTextSize;
// Get the required text heightint 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 sizeswhile (((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 ellipsisif (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {// Draw using a static layoutStaticLayout 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// ellipsiswhile (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 effecttextPaint.setTextSize(targetTextSize);setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registeredif (mTextResizeListener != null) {mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);}
// Reset force resize flagmNeedsResize = false;}

// Set the text size of the text paint object and use a static layout to// render text off screen before measuringprivate int getTextHeight(CharSequence source, TextPaint paint, int width,float textSize) {// Update the text paint objectpaint.setTextSize(textSize);// Draw using a static layoutStaticLayout 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 measuringprivate int getTextWidth(CharSequence source, TextPaint paint, int width,float textSize) {// Update the text paint objectpaint.setTextSize(textSize);// Draw using a static layoutStaticLayout layout = new StaticLayout(source, paint, width,Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);layout.draw(sTextResizeCanvas);return layout.getWidth();}
}



非常感谢Chase发布初始代码。我喜欢通读它,看看它是如何工作的,我很高兴能够添加到它。

我刚刚创建了以下方法(基于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 multiplierfloat mSpacingMult = 1.0f;// Text view additional line spacingfloat 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()方法中使用。

实际上,一个解决方案在Google的对话标题类中……虽然它不如公认的有效,但它更简单,易于适应。

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

我从Chase的解决方案开始,但在我的设备(Galaxy Nexus,Android 4.1)上按预期工作之前,必须适应两件事:

  1. 使用文本绘制的副本来测量布局TextView.get油漆()的留档声明它应该只读使用,所以我在我们使用油漆对象进行测量的两个地方都做了一个副本:

    // 1. in resizeText()if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {// Draw using a static layout// modified: use a copy of TextPaint for measuringTextPaint 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 objectpaint.setTextSize(textSize);...
  2. adding a unit to setting the text size

    // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);setLineSpacing(mSpacingAdd, mSpacingMult);

With these two modifications the solution is working perfectly for me, thanks Chase! I don't know whether it is due to Android 4.x that the original solution was not working. In case you want to see it in action or test whether it really works on your device, you can have a look at my flashcard app Flashcards ToGo where I use this solution to scale the text of a flashcard. The text can have arbitrary length, and the flashcards are displayed in different activities, sometimes smaller sometimes bigger, plus in landscape + portrait mode, and I haven't found any corner case where the solution would not work properly...

适用于Android 4. x的解决方法:

我找到了AutoResizeTextView,它在我的Android 2.1模拟器上运行得很好。我非常喜欢它。但不幸的是,它在我自己的4.0.4手机和4.1模拟器上失败了。尝试后,我发现它可以通过在xml的AutoResizeTextView类中添加以下属性来轻松解决:

android: el唇大小="无"

Android:单行="true"

有了上面的2行,现在AutoResizeTextView可以在我的2.1和4.1模拟器和我自己的4.0.4手机上完美运行。

希望对你有帮助:)

我的实现有点复杂,但附带以下好处:

  • 将可用宽度和可用高度考虑在内
  • 适用于单行和多行标签
  • 使用省略号以防命中最小字体大小
  • 由于内部文本表示已更改,因此会在单独的变量中记住最初设置的文本
  • 确保画布始终只有它需要的大小,而它使用父级的所有可用高度
/*** 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 dipprivate 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 sizeprivate float mMaxTextSize;
// lower bounds for text sizeprivate float mMinTextSize;
// determines whether we're currently in the process of measuring ourselves,// so we do not enter onMeasure recursivelyprivate 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 boundariesprivate 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 widgetmMaxTextSize = 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();}}
@Overridepublic 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 yetif (mOriginalText == null) {return getText();}return mOriginalText;}
@Overridepublic void setText(CharSequence text, BufferType type) {if (!mInMeasure) {mOriginalText = text.toString();}super.setText(text, type);}
@SuppressLint("DrawAllocation")@Overrideprotected 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// textif (mOriginalText == null || mOriginalText.length() == 0 || availableWidth <= 0) {return;}
TextPaint textPaint = getPaint();
// start with the recorded max text sizefloat 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 sizefloat heightMultiplier = availableHeight / (float) textSize.height();float widthMultiplier = availableWidth / (float) textSize.width();float multiplier = Math.min(heightMultiplier, widthMultiplier);targetTextSize = Math.max(targetTextSize * multiplier, mMinTextSize);
// measure againtextSize = 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 lineif (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 lineif (i != lines.length - 1) {modifiedText.append(System.getProperty("line.separator"));}}finalText = modifiedText.toString();
// measure againtextSize = 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_VERTICALif (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// somehowmeasuredHeight = 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 glyphString 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修订]

  • 修正了省略号的位置(off-by-one错误)
  • 重新设计的文本大小计算;现在总是测量包括换行符在内的全文,以解决当添加两个测量行的高度时不会导致与整个文本高度测量相同的结果的问题
  • 而不是循环查找最小的可用文本大小,只需在第一次测量后计算它

我只是借用了一些其他人的想法,并在下面写了一些可能会有帮助的代码。

import android.content.Context;import android.graphics.Canvas;import android.text.Layout.Alignment;import android.text.StaticLayout;import android.text.TextPaint;import android.text.TextUtils;import android.util.AttributeSet;import android.util.TypedValue;import android.widget.TextView;
public class AutoResizeTextView extends TextView {private static final int MAX_SIZE = 1000;
private static final int MIN_SIZE = 5;
private TextPaint mTextPaint;
private float mSpacingMult = 1.0f;
private float mSpacingAdd = 0.0f;
private boolean needAdapt = false;
private boolean adapting = false;
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);init();}
public AutoResizeTextView(Context context, AttributeSet attrs) {super(context, attrs);init();}
public AutoResizeTextView(Context context) {super(context);init();}
private void init() {mTextPaint = new TextPaint();}
@Overrideprotected void onDraw(Canvas canvas) {if (adapting) {return;}if (needAdapt) {adaptTextSize();} else {super.onDraw(canvas);}}
private void adaptTextSize() {CharSequence text = getText();int viewWidth = getMeasuredWidth();int viewHeight = getMeasuredHeight();
if (viewWidth==0 || viewHeight==0|| TextUtils.isEmpty(text)) {return;}
adapting = true;/* binary search */int bottom=MIN_SIZE, top=MAX_SIZE, mid = 0;while (bottom <= top) {mid = (bottom + top)/2;mTextPaint.setTextSize(mid);int textWidth = (int) mTextPaint.measureText(text, 0, text.length());int textHeight = getTextHeight(text, viewWidth);if (textWidth<viewWidth && textHeight<viewHeight) {bottom = mid+1;} else {top = mid-1;}}
int newSize = mid-1;setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
adapting=false;needAdapt = false;
invalidate();}
private int getTextHeight(CharSequence text, int targetWidth) {StaticLayout layout = new StaticLayout(text, mTextPaint, targetWidth,Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);return layout.getHeight();}
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);needAdapt = true;}
@Overrideprotected void onTextChanged(CharSequence text, int start,int lengthBefore, int lengthAfter) {super.onTextChanged(text, start, lengthBefore, lengthAfter);needAdapt = true;}
@Overridepublic void setLineSpacing(float add, float mult) {super.setLineSpacing(add, mult);mSpacingMult = mult;mSpacingAdd = add;}}

感谢Chase和onoelle,对于懒惰的程序员,让我在这里发布他们出色的合并代码的工作版本,在Button上改编,而不是TextView。

用AutoResizeTextButton替换你所有的按钮(不是ImageButton),同样的无聊问题也被修复了。

这是代码。我刚刚删除了导入。

/***            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 viewpublic static final float MIN_TEXT_SIZE = 20;
// Interface for resize notificationspublic interface OnTextResizeListener {public void onTextResize(Button textView, float oldSize, float newSize);}
// Our ellipse stringprivate static final String mEllipsis = "...";
// Registered resize listenerprivate OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resizeprivate boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizingprivate float mTextSize;
// Temporary upper bounds on the starting text sizeprivate float mMaxTextSize = 0;
// Lower bounds for text sizeprivate float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplierprivate float mSpacingMult = 1.0f;
// Text view additional line spacingprivate float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text sizeprivate boolean mAddEllipsis = true;
// Default constructor overridepublic AutoResizeTextButton(Context context) {this(context, null);}
// Default constructor when inflating from XML filepublic AutoResizeTextButton(Context context, AttributeSet attrs) {this(context, attrs, 0);}
// Default constructor overridepublic 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.*/@Overrideprotected 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 sizeresetTextSize();}
/*** If the text view size changed, set the force resize flag to true*/@Overrideprotected 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*/@Overridepublic void setTextSize(float size) {super.setTextSize(size);mTextSize = getTextSize();}
/*** Override the set text size to update our internal reference values*/@Overridepublic void setTextSize(int unit, float size) {super.setTextSize(unit, size);mTextSize = getTextSize();}
/*** Override the set line spacing to update our internal reference values*/@Overridepublic 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*/@Overrideprotected 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 textif(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {return;}
// Get the text view's paint objectTextPaint textPaint = getPaint();
// Store the current text sizefloat oldTextSize = textPaint.getTextSize();// If there is a max text size set, use the lesser of that and the default text sizefloat targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;
// Get the required text heightint 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 sizeswhile(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 ellipsisif(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {// Draw using a static layout// modified: use a copy of TextPaint for measuringTextPaint 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 textif(layout.getLineCount() > 0) {// Since the line at the specific vertical position would be cut off,// we must trim up to the previous lineint lastLine = layout.getLineForVertical(height) - 1;// If the text would not even fit on a single line, clear itif(lastLine < 0) {setText("");}// Otherwise, trim to the previous line and add an ellipsiselse {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 ellipsiswhile(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 registeredif(mTextResizeListener != null) {mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);}
// Reset force resize flagmNeedsResize = false;}
// Set the text size of the text paint object and use a static layout to render text off screen before measuringprivate 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 objectTextPaint paint = new TextPaint(originalPaint);paint.setTextSize(textSize);// Measure using a static layoutStaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);return layout.getHeight();}
}

用法:

在xml中放置一个AutoResizeTextButton以替换普通按钮,而不更改别的什么都行。在onCreate()put中(例如):

    myButton = (AutoResizeTextButton)getView().findViewById(id.myButton);myButton.setMinTextSize(8f);myButton.resizeText();

这是另一个解决方案,只是为了好玩。它可能不是很有效,但它确实可以处理文本的高度和宽度,以及标记的文本。

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

更新:以下代码也满足理想 AutoScaleTextView的要求,如下所述:适用于Android的自动适配文本视图并标记为赢家。

更新2:添加了对maxline的支持,现在在API级别16之前工作正常。

更新3:添加了对android:drawableLeftandroid:drawableRightandroid:drawableTopandroid:drawableBottom标签的支持,这要归功于MartinH的简单修复这里


我的要求有点不同。我需要一种有效的方法来调整大小,因为我在2秒内将一个整数从0动画到TextView中的~4000,我想相应地调整大小。我的解决方案的工作方式有点不同。这是最终结果的样子:

输入图片描述

以及生成它的代码:

<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.AutoResizeTextViewandroid: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.AutoResizeTextViewandroid: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.AutoResizeTextViewandroid: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 constructionmMaxLines = NO_LINE_LIMIT;}mInitiallized = true;}
@Overridepublic void setText(final CharSequence text, BufferType type) {super.setText(text, type);adjustTextSize(text.toString());}
@Overridepublic void setTextSize(float size) {mMaxTextSize = size;mTextCachedSizes.clear();adjustTextSize(getText().toString());}
@Overridepublic void setMaxLines(int maxlines) {super.setMaxLines(maxlines);mMaxLines = maxlines;reAdjust();}
public int getMaxLines() {return mMaxLines;}
@Overridepublic void setSingleLine() {super.setSingleLine();mMaxLines = 1;reAdjust();}
@Overridepublic void setSingleLine(boolean singleLine) {super.setSingleLine(singleLine);if (singleLine) {mMaxLines = 1;} else {mMaxLines = NO_LINE_LIMIT;}reAdjust();}
@Overridepublic void setLines(int lines) {super.setLines(lines);mMaxLines = lines;reAdjust();}
@Overridepublic void setTextSize(int unit, float size) {Context c = getContext();Resources r;
if (c == null)r = Resources.getSystem();elser = c.getResources();mMaxTextSize = TypedValue.applyDimension(unit, size,r.getDisplayMetrics());mTextCachedSizes.clear();adjustTextSize(getText().toString());}
@Overridepublic 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)@Overridepublic 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 linesif (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 matchreturn -1;} else {// too bigreturn 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 returnedreturn lastBest;
}
@Overrideprotected void onTextChanged(final CharSequence text, final int start,final int before, final int after) {super.onTextChanged(text, start, before, after);reAdjust();}
@Overrideprotected void onSizeChanged(int width, int height, int oldwidth,int oldheight) {mTextCachedSizes.clear();super.onSizeChanged(width, height, oldwidth, oldheight);if (width != oldwidth || height != oldheight) {reAdjust();}}}

这是我采取的方法。它非常简单。它使用逐次逼近来确定字体大小,通常可以在不到10次迭代中弄清楚。只需将“actiityWidth”替换为您用于显示文本的任何视图的宽度。在我的示例中,它被设置为屏幕宽度的私有字段。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;}}

我需要一个特定的解决方案。我在布局中有一个编辑文本和文本视图。文本视图的高度和宽度是固定的。当用户开始输入编辑文本时,文本应该立即出现在文本视图中。文本字段中的文本应该自动调整大小以适应文本视图。所以我更新了蔡斯的解决方案为我工作。所以当文本在文本视图中发生变化时,开始调整大小。我的解决方案和蔡斯的解决方案之间的区别:即使用户删除了一些字符,也会进行调整大小。我希望它能帮助别人。

public class TextFitTextView extends TextView {
// Minimum text size for this text viewpublic static final float MIN_TEXT_SIZE = 10;
// Maximum text size for this text view - if it is 0, then the text acts// like match_parentpublic static final float MAX_TEXT_SIZE = 0;
// Our ellipse stringprivate static final String mEllipsis = "...";
// Text size that is set from code. This acts as a starting point for// resizingprivate float mTextSize;
// Lower bounds for text sizeprivate float mMinTextSize = MIN_TEXT_SIZE;
// Max bounds for text sizeprivate float mMaxTextSize = MAX_TEXT_SIZE;
// Text view line spacing multiplierprivate float mSpacingMult = 1.0f;
// Text view additional line spacingprivate float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text sizeprivate boolean mAddEllipsis = true;
// Add ellipsis to text that overflows at the smallest text sizeprivate int heightLimit;private int widthLimit;
// Default constructor overridepublic TextFitTextView(Context context) {this(context, null);}
// Default constructor when inflating from XML filepublic TextFitTextView(Context context, AttributeSet attrs) {this(context, attrs, 0);}
// Default constructor overridepublic TextFitTextView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);mTextSize = getTextSize();}
/*** When text changes resize the text size.*/@Overrideprotected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {// if we are adding new chars to textif (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*/@Overridepublic void setTextSize(float size) {super.setTextSize(size);mTextSize = getTextSize();}
/*** Override the set text size to update our internal reference values*/@Overridepublic void setTextSize(int unit, float size) {super.setTextSize(unit, size);mTextSize = getTextSize();}
/*** Override the set line spacing to update our internal reference values*/@Overridepublic 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*/@Overrideprotected 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// textif (text == null || text.length() == 0 || heightLimit <= 0 || widthLimit <= 0 || mTextSize == 0) {return;}
// Get the text view's paint objectTextPaint textPaint = getPaint();
// Get the required text heightint 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 sizesif (increase) {while (textHeight > heightLimit && mTextSize > mMinTextSize) {mTextSize = Math.max(mTextSize - 2, mMinTextSize);textHeight = getTextHeight(text, textPaint, widthLimit, mTextSize);}}//      text length has been decreasedelse {//          if max test size is set then add it to while conditionif (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 ellipsisif (mAddEllipsis && mTextSize == mMinTextSize && textHeight > heightLimit) {// Draw using a static layoutTextPaint 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 textif (layout.getLineCount() > 0) {// Since the line at the specific vertical position would be cut// off,// we must trim up to the previous lineint lastLine = layout.getLineForVertical(heightLimit) - 1;// If the text would not even fit on a single line, clear itif (lastLine < 0) {setText("");}// Otherwise, trim to the previous line and add an ellipsiselse {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// ellipsiswhile (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 effectsetTextSize(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 measuringprivate int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {// Update the text paint objectTextPaint paint = new TextPaint(originalPaint);paint.setTextSize(textSize);// Measure using a static layoutStaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd,true);return layout.getHeight();}
}

我需要调整文本大小以完美地适应视图边界。Chase的解决方案只减少文本大小,如果有足够的空间,这个解决方案也会放大文本。

为了制作所有快速和精确,我使用了二分法而不是迭代法,正如你在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 viewpublic static final float MIN_TEXT_SIZE = 26;
// Maximum text size for this text viewpublic static final float MAX_TEXT_SIZE = 128;
private static final int BISECTION_LOOP_WATCH_DOG = 30;
// Interface for resize notificationspublic interface OnTextResizeListener {public void onTextResize(TextView textView, float oldSize, float newSize);}
// Our ellipse stringprivate static final String mEllipsis = "...";
// Registered resize listenerprivate OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resizeprivate boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for resizingprivate float mTextSize;
// Temporary upper bounds on the starting text sizeprivate float mMaxTextSize = MAX_TEXT_SIZE;
// Lower bounds for text sizeprivate float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplierprivate float mSpacingMult = 1.0f;
// Text view additional line spacingprivate float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text sizeprivate boolean mAddEllipsis = true;
// Default constructor overridepublic AutoResizeTextView(Context context) {this(context, null);}
// Default constructor when inflating from XML filepublic AutoResizeTextView(Context context, AttributeSet attrs) {this(context, attrs, 0);}
// Default constructor overridepublic 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.*/@Overrideprotected 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 sizeresetTextSize();}
/*** If the text view size changed, set the force resize flag to true*/@Overrideprotected 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*/@Overridepublic void setTextSize(float size) {super.setTextSize(size);mTextSize = getTextSize();}
/*** Override the set text size to update our internal reference values*/@Overridepublic void setTextSize(int unit, float size) {super.setTextSize(unit, size);mTextSize = getTextSize();}
/*** Override the set line spacing to update our internal reference values*/@Overridepublic 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*/@Overrideprotected 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 textif(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {return;}
// Get the text view's paint objectTextPaint textPaint = getPaint();
// Store the current text sizefloat oldTextSize = textPaint.getTextSize();
// Bisection method: fast & precisefloat 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;elselower = 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 ellipsisif(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {// Draw using a static layout// modified: use a copy of TextPaint for measuringTextPaint 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 textif(layout.getLineCount() > 0) {// Since the line at the specific vertical position would be cut off,// we must trim up to the previous lineint lastLine = layout.getLineForVertical(height) - 1;// If the text would not even fit on a single line, clear itif(lastLine < 0) {setText("");}// Otherwise, trim to the previous line and add an ellipsiselse {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 ellipsiswhile(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 effectsetTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registeredif(mTextResizeListener != null) {mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);}
// Reset force resize flagmNeedsResize = false;}
// Set the text size of the text paint object and use a static layout to render text off screen before measuringprivate 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 objectpaint.setTextSize(textSize);// Measure using a static layoutStaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);return layout.getHeight();}
}

警告,bug在Android蜂巢和冰淇淋三明治

Android版本:3.1-4.04有一个bug,TextView内部的setTextSize()仅适用于第一次(第一次调用)。

错误在这里描述:http://code.google.com/p/android/issues/detail?id=22493http://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);

我在文本的左侧和右侧添加了这个""字符,以保持它居中。如果它与左侧对齐,则仅附加到右侧。当然,它也可以嵌入AutoResizeTextView小部件,但我想将修复代码保留在外部。

我使用了Chase和M-WaJeEh的代码我发现了一些优点和缺点

从追逐

优势:

  • 它非常适合1行TextView

缺点:

  • 如果使用自定义字体超过1行,一些文本将消失

  • 如果它启用了椭圆,它没有为椭圆准备空间

  • 如果是自定义字体(字体),则不支持

来自M-WaJeEh

优势:

  • 非常适合多线路

缺点:

  • 如果将高度设置为包装内容,则此代码将从最小大小开始,并尽可能减少到最小,而不是从setSize开始并减少有限的宽度

  • 如果是自定义字体(字体),则不支持

我结合了上面的一些建议,用二分法制作了一个向上和向下缩放的方法。它也在宽度范围内缩放。

/***               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 viewpublic static final float MIN_TEXT_SIZE = 10;
// Minimum text size for this text viewpublic static final float MAX_TEXT_SIZE = 128;
private static final int BISECTION_LOOP_WATCH_DOG = 30;
// Interface for resize notificationspublic interface OnTextResizeListener {public void onTextResize(TextView textView, float oldSize, float newSize);}
// Our ellipse stringprivate static final String mEllipsis = "...";
// Registered resize listenerprivate OnTextResizeListener mTextResizeListener;
// Flag for text and/or size changes to force a resizeprivate boolean mNeedsResize = false;
// Text size that is set from code. This acts as a starting point for// resizingprivate float mTextSize;
// Temporary upper bounds on the starting text sizeprivate float mMaxTextSize = MAX_TEXT_SIZE;
// Lower bounds for text sizeprivate float mMinTextSize = MIN_TEXT_SIZE;
// Text view line spacing multiplierprivate float mSpacingMult = 1.0f;
// Text view additional line spacingprivate float mSpacingAdd = 0.0f;
// Add ellipsis to text that overflows at the smallest text sizeprivate boolean mAddEllipsis = true;
// Default constructor overridepublic AutoResizeTextView(Context context) {this(context, null);}
// Default constructor when inflating from XML filepublic AutoResizeTextView(Context context, AttributeSet attrs) {this(context, attrs, 0);}
// Default constructor overridepublic 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.*/@Overrideprotected 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 sizeresetTextSize();}
/*** If the text view size changed, set the force resize flag to true*/@Overrideprotected 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*/@Overridepublic void setTextSize(float size) {super.setTextSize(size);mTextSize = getTextSize();}
/*** Override the set text size to update our internal reference values*/@Overridepublic void setTextSize(int unit, float size) {super.setTextSize(unit, size);mTextSize = getTextSize();}
/*** Override the set line spacing to update our internal reference values*/@Overridepublic 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*/
@Overrideprotected 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 heightint 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// textif (text == null || text.length() == 0 || height <= 0 || width <= 0|| mTextSize == 0) {return;}
// Get the text view's paint objectTextPaint textPaint = getPaint();
// Store the current text sizefloat oldTextSize = textPaint.getTextSize();
// Bisection method: fast & precisefloat 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;elselower = 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 ellipsisif (mAddEllipsis && targetTextSize == mMinTextSize&& textHeight > height) {// Draw using a static layout// modified: use a copy of TextPaint for measuringTextPaint 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 textif (layout.getLineCount() > 0) {// Since the line at the specific vertical position would be cut// off,// we must trim up to the previous lineint lastLine = layout.getLineForVertical(height) - 1;// If the text would not even fit on a single line, clear itif (lastLine < 0) {setText("");}// Otherwise, trim to the previous line and add an ellipsiselse {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// ellipsiswhile (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 effectsetTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);setLineSpacing(mSpacingAdd, mSpacingMult);
// Notify the listener if registeredif (mTextResizeListener != null) {mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);}
// Reset force resize flagmNeedsResize = false;}
// Set the text size of the text paint object and use a static layout to// render text off screen before measuringprivate 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 objectpaint.setTextSize(textSize);// Measure using a static layoutStaticLayout 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 measuringprivate int getTextWidth(CharSequence source, TextPaint originalPaint,int width, float textSize) {// Update the text paint objectTextPaint paint = new TextPaint(originalPaint);// Draw using a static layoutpaint.setTextSize(textSize);
StaticLayout layout = new StaticLayout(source, paint, width,Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
return (int) layout.getLineWidth(0);}}

扩展TextView并使用下面的代码覆盖onDraw。它将保持文本长宽比,但调整大小以填充空间。如果需要,您可以轻松修改代码以拉伸。

  @Overrideprotected 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 enoughbreak;}
float adjustX = desiredWidth / width;float adjustY = desiredHeight / height;
textSize = textSize * (adjustY < adjustX ? adjustY : adjustX);
// adjust text sizetextPaint.setTextSize(textSize);}float x = desiredWidth / 2f;float y = desiredHeight / 2f - rect.top - rect.height() / 2f;canvas.drawText(text, x, y, textPaint);}

这里参考ScalableTextView.java适用于Android的自动适配文本视图。我添加了根据文本长度收缩和展开TextView的代码

这是一个简单的解决方案,它使用TextView本身并添加TextChangedList的:

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() {@Overridepublic void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Overridepublic void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Overridepublic 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边界。

由于我一直在寻找这个问题,并且我发现了一个解决方案,而这个解决方案在这里缺少,我将在这里写出来,以供将来参考。

注意:此代码直接来自Google Android Lollipop拨号器,我不记得当时是否进行了更改。此外,我不知道这是哪个许可证,但我有理由认为它是Apache 2.0

类#0,实际的#1

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;}@Overrideprotected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {super.onTextChanged(text, start, lengthBefore, lengthAfter);ViewUtil.resizeText(this, mOriginalTextSize, mMinTextSize);}@Overrideprotected 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.ResizeTextViewandroid:layout_width="match_parent"android:layout_height="64dp"android:gravity="center"android:maxLines="1"/>

希望有帮助!

AppcompatTextView现在支持从支持库26.0开始的自动调整大小。Android O中的TextView也以相同的方式工作。更多信息可以在这里找到。可以找到这里一个简单的演示应用程序。

<LinearLayoutxmlns: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">
<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:autoSizeTextType="uniform"app:autoSizeMinTextSize="12sp"app:autoSizeMaxTextSize="100sp"app:autoSizeStepGranularity="2sp"/>
</LinearLayout>

如果有人需要它,这里有相同的代码片段,但适用于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;}}}

在2017年的谷歌IO大会上,谷歌推出了TextView的自动大小属性

https://youtu.be/fjUdJ2aVqE4

<android.support.v7.widget.AppCompatTextViewandroid: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"/>

为那些在Xamarin. Android上编码的人提供重写在c#上的这个版本的顶级答案。对我很有效。

 /***               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 viewset{_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 viewset{_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 sizeResetTextSize();}
/// <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 textif (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 objectvar textPaint = Paint;// Store the current text sizevar oldTextSize = textPaint.TextSize;// If there is a max text size set, use the lesser of that and the default text sizevar targetTextSize = _mMaxTextSize > 0 ? System.Math.Min(_textSize, _mMaxTextSize) : _textSize;
// Get the required text heightvar 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 sizeswhile (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 ellipsisif (ShouldAddEllipsis && targetTextSize == _mMinTextSize && textHeight > height){// Draw using a static layout// modified: use a copy of TextPaint for measuringvar paint = new TextPaint(textPaint);// Draw using a static layoutvar layout = new StaticLayout(text, paint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, false);
// Check that we have a least one line of rendered textif (layout.LineCount > 0){// Since the line at the specific vertical position would be cut off,// we must trim up to the previous linevar lastLine = layout.GetLineForVertical(height) - 1;// If the text would not even fit on a single line, clear itif (lastLine < 0){Text = string.Empty;}// Otherwise, trim to the previous line and add an ellipsiselse{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 ellipsiswhile (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 effectSetTextSize(ComplexUnitType.Px, targetTextSize);SetLineSpacing(_spacingAdd, _spacingMult);
var notifyArgs = new OnTextResizeEventArgs{TextView = this,NewSize = targetTextSize,OldSize = oldTextSize};
// Notify the listener if registeredOnTextResize?.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 objectvar paintCopy = new TextPaint(paint){TextSize = textSize};
// Measure using a static layoutvar layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, true);
return layout.Height;}}}

我的方法是:

public void changeTextSize(int initialSize, TextView tv) {
DisplayMetrics displayMetrics = new DisplayMetrics();getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);double width = displayMetrics.widthPixels / displayMetrics.xdpi;double height = displayMetrics.heightPixels / displayMetrics.ydpi;
Log.i("LOG", "The width of the tested emulator is: " + width);Log.i("LOG", "The height of the tested emulator is: " + height);
double scale = Math.min(width / 2.25, height / 4.0); //See the logcat >>> width = 2.25 and heigt = 4.0tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, (int) (initialSize * scale));
}

例如:

changeTextSize(16, findViewById(R.id.myTextView));changeTextSize(12, findViewById(R.id.myEditText));

从2018年6月起,Android正式开始支持Android 4.0(API级别14)及更高版本。
点击查看:自动调整文本视图大小

使用Android 8.0(API级别26)及更高版本

<?xml version="1.0" encoding="utf-8"?><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:autoSizeTextType="uniform"android:autoSizeMinTextSize="12sp"android:autoSizeMaxTextSize="100sp"android:autoSizeStepGranularity="2sp" />

编程方式:

setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize,int autoSizeStepGranularity, int unit)
textView.setAutoSizeTextTypeUniformWithConfiguration(1, 17, 1, TypedValue.COMPLEX_UNIT_DIP);


Android 8.0之前的Android版本(API级别26)

<?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns: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="match_parent">
<TextViewandroid:layout_width="match_parent"android:layout_height="200dp"app:autoSizeTextType="uniform"app:autoSizeMinTextSize="12sp"app:autoSizeMaxTextSize="100sp"app:autoSizeStepGranularity="2sp" />
</LinearLayout>

编程方式:

TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(TextView textView, int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(textView, 1, 17, 1,TypedValue.COMPLEX_UNIT_DIP);

注意:TextView必须layout_width="match_parent"或绝对大小!

问题是关于如何在Button上拥有此功能;对于TextView,它很容易,并且可以按照官方文档很好地工作

Style.xml:

    <style name="Widget.Button.CustomStyle" parent="Widget.MaterialComponents.Button"><item name="android:minHeight">50dp</item><item name="android:maxWidth">300dp</item><item name="android:textStyle">bold</item><item name="android:textSize">16sp</item><item name="backgroundTint">@color/white</item><item name="cornerRadius">25dp</item><item name="autoSizeTextType">uniform</item><item name="autoSizeMinTextSize">10sp</item><item name="autoSizeMaxTextSize">16sp</item><item name="autoSizeStepGranularity">2sp</item><item name="android:maxLines">1</item><item name="android:textColor">@color/colorPrimary</item><item name="android:insetTop">0dp</item><item name="android:insetBottom">0dp</item><item name="android:lineSpacingExtra">4sp</item><item name="android:gravity">center</item></style>

用法:

<com.google.android.material.button.MaterialButtonandroid:id="@+id/blah"style="@style/Widget.Button.CustomStyle"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginStart="16dp"android:layout_marginEnd="16dp"android:text="Your long text, to the infinity and beyond!!! Why not :)" />

结果:
结果

文本到适合边界(1行)

使文本收缩以适合一行的边界:

<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"app:autoSizeTextType="uniform"android:lines:"1"/>