【问题标题】:How to adjust text font size to fit textview如何调整文本字体大小以适合 textview
【发布时间】:2011-02-06 17:14:24
【问题描述】:

在 android 中有什么方法可以调整 textview 中的 textsize 以适应它所占据的空间?

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

有什么想法吗?

我试过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());  

【问题讨论】:

标签: android font-size textview


【解决方案1】:

以下解决方案包含此处的所有建议。它从 Dunni 最初发布的内容开始。它使用类似于 gjpc 的二进制搜索,但它更具可读性。它还包括 gregm 的错误修复和我自己的错误修复。

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

【讨论】:

  • 感谢您整合所有反馈。我看到该解决方案考虑了 only width。我的问题是喜欢超过高度。
  • 哦,它似乎在运行时部分工作。在设备或模拟器上,文本被剪掉一半。在 Eclipse 布局编辑器上,它看起来不错。有什么想法吗?
  • 这篇文章看起来可以解决问题(为 hi/lo 添加自定义 xml 属性):stackoverflow.com/a/8090772/156611
  • 这是一个很棒的解决方案!如果其他人是 android 开发新手并且不太了解如何在 XML 中实现扩展视图,它看起来像这样:&lt;com.example.zengame1.FontFitTextView android:paddingTop="5dip" android:id="@+id/childs_name" android:layout_width="fill_parent" android:layout_height="0dip" android:layout_weight="1" android:layout_gravity="center" android:textSize="@dimen/text_size"/&gt;
  • 干得好!让它也可以与按钮一起使用。要注意 textview 的高度,只需设置 float hi = this.getHeight() - this.getPaddingBottom() - this.getPaddingTop();
【解决方案2】:

我编写了一个扩展 TextView 并执行此操作的类。它只是按照您的建议使用 measureText 。基本上,它有一个最大文本大小和最小文本大小(可以更改),它只是以 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;

}

【讨论】:

【解决方案3】:

这是speedplane's 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" />

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

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

【讨论】:

  • 你是如何使用它的?我在答案中添加了一个如何使用它的示例。我已经使用各种 android 版本、模拟器和 ADT 图形布局编辑器对其进行了测试。
  • 基本上,我没有指定具体的高度。如果需要,您的 onMeasure 允许视图接管。我设法想出了一个解决方案,将例程中的最后一行更改为this.setMeasuredDimension(parentWidth, height);
  • 很好的输入,我更正了代码:) 现在它可以与match_parentwrap_content 一起使用。
  • 你为什么要测试空字符串写成 "".equals(s) 而不是简单的 s.isEmpty() ?还是 s.length()==0?不明白为什么我有时会看到这些相等测试。
  • @PratikButani 感谢您指出这一点。相当于text.isEmpty() 将是"".equals(text)
【解决方案4】:

这是我的解决方案,适用于模拟器和手机,但不适用于 Eclipse 布局编辑器。它的灵感来自 kilaka 的代码,但文本的大小不是从 Paint 中获得的,而是通过调用 measure(0, 0) 测量 TextView 本身获得的。

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>

查看my github 获取该课程的最新版本。 我希望它对某人有用。 如果发现错误或代码需要解释,请随时在 Github 上打开问题。

【讨论】:

  • 它在我的 Galaxy Nexus 上运行良好,但我在小屏幕设备上遇到了问题。
  • 小屏幕设备有什么问题?
  • 抱歉,问题不在于小屏幕,您的视图不适用于所有搭载 Android 4.0 的设备,包括模拟器。我在你的 GitHub 中打开了新问题
【解决方案5】:

非常感谢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",如示例所示。

【讨论】:

  • 对不起,但它仍然不适用于所有情况。我也认为它不能正确处理多行。
  • 哦.. 你能给我一个它不起作用的案例吗? (不支持多行,你是对的)
  • 很多情况。我创建了一个随机测试器只是为了证明它是正确的。这是一个示例:视图宽度:317px,高度:137px,文本:“q7Lr”。我看到的是:tinypic.com/view.php?pic=2dv5yf9&s=6。这是我制作的示例项目:mega.co.nz/…。我认为这样的视图应该处理多行,支持任何大小的字体,处理高度而不仅仅是宽度,......可悲的是,我发现的样本都没有很好地工作。
  • 好的,绝对不是一个完整的解决方案。对不起。暂时没有时间改进它。如果你能改进它,不要犹豫发布你的解决方案。
  • 我创建了一个新线程来显示我的测试,希望有人能够提出一个好的解决方案:stackoverflow.com/questions/16017165/…
【解决方案6】:

您现在可以在没有第三方库或小部件的情况下执行此操作。它内置在 API 级别 26 的 TextView 中。将 android:autoSizeTextType="uniform" 添加到您的 TextView 并为其设置高度。就这样。使用 app:autoSizeTextType="uniform" 以实现向后兼容性

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" 来实现向后兼容性,因为 android:autoSizeTextType="uniform" 仅适用于 API 级别 26 及更高级别。
【解决方案7】:

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

Auto Scale TextView Text to Fit within Bounds

【讨论】:

    【解决方案8】:

    使用app:autoSizeTextType="uniform" 是为了向后兼容,因为android:autoSizeTextType="uniform" 仅适用于 API 级别 26 及更高级别。

    【讨论】:

    • 谢谢苏拉杰。这是最好的解决方案
    • 我在 SDK 24 上检查过,但它不起作用。你确定app:.. 将适用于 SDK 低 26?
    • 是的,它作为应用命名空间工作,提供向后兼容性。有一篇关于它的详细文章,检查一下它可能会解决你的问题。 medium.com/over-engineering/…
    【解决方案9】:

    对 onMeasure 稍作修改:

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

    【讨论】:

    • 代替二分搜索,简单的缩放效果很好:trySize *= availableWidth / measured_width(然后限制为 minTextSize)。
    【解决方案10】:

    我发现以下内容非常适合我。它不会循环并同时考虑高度和宽度。请注意,在视图上调用 setTextSize 时指定 PX 单元很重要。感谢上一篇文章中的提示!

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

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

    private static final String text10="OOOOOOOOOO";
    public static Paint adjustTextSize(Paint paint, int numCharacters, int widthPixels, int heightPixels) {
        float width = paint.measureText(text10)*numCharacters/text10.length();
        float newSize = (int)((widthPixels/width)*paint.getTextSize());
        paint.setTextSize(newSize);
    
        // remeasure with font size near our desired result
        width = paint.measureText(text10)*numCharacters/text10.length();
        newSize = (int)((widthPixels/width)*paint.getTextSize());
        paint.setTextSize(newSize);
    
        // Check height constraints
        FontMetricsInt metrics = paint.getFontMetricsInt();
        float textHeight = metrics.descent-metrics.ascent;
        if (textHeight > heightPixels) {
            newSize = (int)(newSize * (heightPixels/textHeight));
            paint.setTextSize(newSize);
        }
    
        return paint;
    }
    

    【讨论】:

    • 如何将其合并到布局 xml 中?
    • 此代码可用于将现有视图中的文本放置到受限大小区域中,或者您可以从 TextView 创建自己的派生类并覆盖 onMeasure,如其他帖子所示。它本身不能用于布局。
    • 确保检查视图的尺寸它被绘制之后。在 onCreate() 期间这样做还为时过早。我使用 ViewTreeObserver 确保在正确的时间进行了测量。
    【解决方案11】:

    适用于修改

    您需要像这样设置文本视图大小,否则 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);
    }
    

    【讨论】:

      【解决方案12】:

      在我找到这个库之前,我在我的项目中经历了很长时间的痛苦:

      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"
      />
      

      【讨论】:

        【解决方案13】:

        我使用了上述 Dunni 解决方案的变体,但该特定代码对我不起作用。特别是,当尝试使用 Paint 对象设置为具有视图的 Paint 对象的特征,然后调用 measureText() 时,它不会返回与直接调用视图的 Paint 对象相同的值。也许我的观点设置方式存在一些差异,导致行为不同。

        我的解决方案是直接使用视图的 Paint,即使多次更改视图的字体大小可能会降低性能。

        【讨论】:

          【解决方案14】:

          我一直在努力改进 speedplane 的出色解决方案,并提出了这个问题。它管理高度,包括设置边距以使文本正确垂直居中。

          这使用相同的函数来获取宽度,因为它似乎工作得最好,但它使用不同的函数来获取高度,因为高度没有在任何地方提供。需要进行一些更正,但我想出了一种方法来做到这一点,同时看起来令人赏心悦目。

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

          【讨论】:

            【解决方案15】:

            谷歌已经做了这个功能。

            <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

            【讨论】:

              【解决方案16】:

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

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

              【讨论】:

                【解决方案17】:
                /* 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;
                

                【讨论】:

                • 通过简单地阅读您的代码,我认为这不会起作用。请记住,问题是关于自动调整文本视图的字体大小以适应视图。您设置了固定的字体大小。
                【解决方案18】:

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

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

                【讨论】:

                  【解决方案19】:

                  使用下面的代码扩展 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);
                    }
                  

                  【讨论】:

                    【解决方案20】:

                    我编写了一个简短的帮助类,它使文本视图适合特定宽度,如果无法达到最小文本大小,则在末尾添加 ellipsize "..."。

                    请记住,它只会使文本变小,直到适合或达到最小文本大小。要使用大尺寸进行测试,请在调用帮助方法之前将 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);
                            }
                        }
                    }
                    

                    【讨论】:

                    • 这个支持多行文字吗?
                    【解决方案21】:

                    如果设置了像 allCaps 这样的转换,speedplane 的方法是错误的。我修复了它,产生了以下代码(对不起,我的名声不允许我将此作为评论添加到 speedplane 的解决方案中):

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

                    【讨论】:

                      【解决方案22】:

                      我不知道这是正确的方法还是它的工作方式不正确...查看您的视图并检查 OnGlobalLayoutListener() 并获取 textview linecount 然后设置 textSize。

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

                      非常简单的几行代码..

                      【讨论】:

                        【解决方案23】:

                        在我的情况下,使用 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)
                                }
                            }
                        }
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 2019-09-20
                          • 2016-04-26
                          • 2011-11-24
                          • 2015-12-10
                          • 2015-09-16
                          • 1970-01-01
                          相关资源
                          最近更新 更多