如何调整文本字体大小,以适应文本视图

在 android 中有没有办法调整文本视图中的文本大小以适应它所占据的空间?

例如,我正在使用一个 TableLayout,并在每一行中添加几个 TextView。因为我不希望 TextView包装的文本,我宁愿看到它降低了内容的字体大小。

有什么想法吗?

我试过 measureText,但由于我不知道列的大小,使用起来似乎很麻烦。 这是代码,我想改变字体大小的东西,适合

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

我已经编写了一个类来扩展 TextView 并实现这一点。它只是按照您的建议使用 testureText。基本上,它有一个最大的文本大小和最小的文本大小(可以更改) ,它只是运行之间的大小递减为1,直到它找到最大的一个将适合。不是特别优雅,但我不知道还有什么别的办法。

密码如下:

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


public class FontFitTextView extends TextView {


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


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


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


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


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


this.setTextSize(trySize);
}
}


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


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


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


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


public float getMaxTextSize() {
return maxTextSize;
}


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


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


}

我使用了上面 Dunni 解决方案的一个变体,但是那个特定的代码对我不起作用。特别是,当尝试使用 Paint 对象集来拥有视图的 Paint 对象的特性,然后调用 testureText ()时,它不会返回与直接调用视图的 Paint 对象相同的值。也许在我的观点建立的方式上有一些差异,使得行为不同。

我的解决方案是直接使用视图的 Paint,尽管多次更改视图的字体大小可能会带来一些性能损失。

可以进行修改

您需要像这样设置文本视图大小,因为否则 setTextSize 假设值是 SP 单位:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

您需要显式地添加这些代码。

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

稍微修改一下 onTest:

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

在 refitText 上进行二进制搜索:

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


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


this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
}
}

我也遇到了同样的问题,所以我写了一个似乎对我有用的类。基本上,我使用静态布局在一个单独的画布上绘制文本,然后重新测量,直到找到合适的字体大小。您可以在下面的主题中看到发布的类。希望能有所帮助。

自动缩放文本视图文本以适应边界

我发现以下几点对我很有用。它不会循环,同时考虑高度和宽度。请注意,在视图上调用 setTextSize 时,指定 PX 单元非常重要。感谢在前一篇文章中为此提供的提示!

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

下面是我使用的例程,从视图传入 getPaint ()。使用带有“ wide”字符的10个字符串来估计与实际字符串无关的宽度。

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


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


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


return paint;
}

下面的解决方案包含了这里的所有建议。它开始于邓尼最初发布的内容。它使用类似于 gjpc 的二进制搜索,但是可读性更强一些。它还包括 Gregm 的 bug 修复和我自己的 bug 修复。

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


public class FontFitTextView extends TextView {


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


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


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


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


mTestPaint.set(this.getPaint());


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


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


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


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


//Attributes
private Paint mTestPaint;
}

受以前海报的启发,我想分享我的解决方案。它使用一个比例因子,这个比例因子应用于以前的字体大小,使其适合可用的空间。除了防止 TextViewsonDraw 方法的意外行为之外,它还可以自己绘制文本。

public class FontFitTextView extends TextView {


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


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


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


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


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


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


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


private void calcTextSize(String s, Canvas c) {


float availableHeight = viewHeight;
float availableWidth = viewWidth;


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


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


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


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


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


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


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


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


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


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


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

这是 高速飞机是 FontFitTextView,但它只有 减少字体大小,如果需要使文本适合,并保持其字体大小,否则。它不增加字体大小以适应高度。

public class FontFitTextView extends TextView {


// Attributes
private Paint mTestPaint;
private float defaultTextSize;


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


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


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


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


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


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


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


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


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


}


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


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


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


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


}

下面是如何在 xml 中使用它的示例:

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

这将保持字体大小为60只要文字适合的宽度。如果文本较长,则会减小字体大小。在这种情况下,TextView的高度也会因为 height=wrap_content而改变。

如果您发现任何错误,请随意编辑。

下面是我的解决方案,它可以在模拟器和电话上工作,但是在 Eclipse 布局编辑器上效果不是很好。它的灵感来自 kilaka 的代码,但是文本的大小不是从 Paint 获得的,而是通过测量 TextView 本身称为 measure(0, 0)

Java 类:

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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


public int getMinTextSize() {
return minTextSize;
}


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


public int getMaxTextSize() {
return maxTextSize;
}


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

XML 属性文件:

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

检查 我的小弟弟以获得该类的最新版本。 我希望它对某些人有用。 如果发现一个 bug 或者代码需要解释,请随意在 Github 上打开一个问题。

我一直致力于改进极速飞机的优秀解决方案,并且想出了这个。它管理高度,包括设置边距,使文本应该正确地垂直居中。

这使用相同的函数来获取宽度,因为它似乎工作得最好,但是它使用不同的函数来获取高度,因为高度不是在任何地方提供的。有些地方需要修正,但我想出了一个办法,既能看起来赏心悦目,又能做到这一点。

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


public class FontFitTextView extends TextView {


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


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


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


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


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


Rect bounds = new Rect();


mTestPaint.set(this.getPaint());


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


}


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


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


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


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


//Attributes
private Paint mTestPaint;
}

非常感谢 https://stackoverflow.com/users/234270/speedplane。答得好!

下面是他的回复的一个改进版本,它也考虑了身高,并附带了一个 maxFontSize 属性来限制字体大小(在我的案例中很有用,所以我想与大家分享) :

package com.<your_package>;


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




public class FontFitTextView extends TextView
{


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


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


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


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


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


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


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


mTestPaint.set(this.getPaint());


Rect bounds = new Rect();


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


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


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


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


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


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


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

相应的/res/values/attr.xml 文件:

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


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


</resources>

例如:

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


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


...
</RelativeLayout>

要使用新的 maxFontSize属性,不要忘记添加 xmlns:res-auto="http://schemas.android.com/apk/res-auto",如示例所示。

/* get your context */
Context c = getActivity().getApplicationContext();


LinearLayout l = new LinearLayout(c);
l.setOrientation(LinearLayout.VERTICAL);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0);


l.setLayoutParams(params);
l.setBackgroundResource(R.drawable.border);


TextView tv=new TextView(c);
tv.setText(" your text here");


/* set typeface if needed */
Typeface tf = Typeface.createFromAsset(c.getAssets(),"fonts/VERDANA.TTF");
tv.setTypeface(tf);


// LayoutParams lp = new LayoutParams();


tv.setTextColor(Color.parseColor("#282828"));


tv.setGravity(Gravity.CENTER | Gravity.BOTTOM);
//  tv.setLayoutParams(lp);


tv.setTextSize(20);
l.addView(tv);


return l;

这应该是一个简单的解决方案:

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


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


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


textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

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

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


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


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


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


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


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


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


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


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

我编写了一个简短的助手类,使文本视图在一定的宽度内适合,并在最小文本大小无法实现的情况下在结尾处添加椭圆大小“ ...”。

请记住,它只是使文本变得更小,直到它适合或直到达到最小文本大小。若要测试较大的大小,请在调用 help 方法之前将 textsize 设置为较大的数字。

它需要像素,所以如果你使用来自 dimen 的值,你可以这样调用它:


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

这是我使用的类:


public class WidgetUtils {


public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
textView.setEllipsize(null);
int size = (int)textView.getTextSize();
while (true) {
Rect bounds = new Rect();
Paint textPaint = textView.getPaint();
textPaint.getTextBounds(text, 0, text.length(), bounds);
if(bounds.width() < maxWidthPx){
break;
}
if (size <= minTextSizePx) {
textView.setEllipsize(TextUtils.TruncateAt.END);
break;
}
size -= 1;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
}
}
}

如果设置了像 allCaps 这样的转换,那么“高速飞机”的进近就有问题。我修复了它,结果是下面的代码(对不起,我的名声不允许我把它作为一个注释添加到快速飞机的解决方案) :

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


public class FontFitTextView extends TextView {


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


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


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


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


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


mTestPaint.set(this.getPaint());


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


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


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


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


//Attributes
private Paint mTestPaint;
}

我不知道这是否是正确的方式,但它的工作... 采取您的视图,并检查 OnGlobalLayoutListener () ,得到文本视图行数,然后设置 textSize。

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

它非常简单的几行代码. 。

在我的项目中,这种痛苦持续了很长时间,直到我发现了这个图书馆:

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

您只需要根据需要添加 xml,就可以完成了。例如:

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

您现在可以在没有第三方库或小部件的情况下完成此操作。它内置在 API 级别26的 TextView 中。将 android:autoSizeTextType="uniform"添加到您的 TextView并为其设置高度。仅此而已。

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

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

你也可以使用 TextViewCompat或者 app:autoSizeTextType="uniform"向下兼容。

谷歌已经做了这个功能。

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

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

使用 app:autoSizeTextType="uniform"向下兼容,因为 android:autoSizeTextType="uniform"只能在 API 26级或更高级别下工作。

在我的案例中,使用 app:autoSize并不能解决所有的问题,例如,它不能防止断字

这就是我最终使用的,它将调整文本大小,以便没有多行断字

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


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


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


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


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