如何缩放/调整文本以适应TextView?

31

我正在尝试创建一个方法,用于调整多行文本在 TextView 中的大小,以使其适合 TextView 的边界(X和Y维度)。

目前,我已经有一些代码,但它只能将文本大小调整到仅填充 TextView 的第一个字母/字符的维度(即只有第一个字母可见,且非常大)。我需要它将所有文本行都适应 TextView 边界。

这是我目前的代码:

public static void autoScaleTextViewTextToHeight(TextView tv)
{
    final float initSize = tv.getTextSize();
    //get the width of the view's back image (unscaled).... 
    float minViewHeight;
    if(tv.getBackground()!=null)
    {
      minViewHeight = tv.getBackground().getIntrinsicHeight();
    }
    else
    {
      minViewHeight = 10f;//some min.
    }
    final float maxViewHeight = tv.getHeight() - (tv.getPaddingBottom()+tv.getPaddingTop())-12;// -12 just to be sure
    final String s = tv.getText().toString();

    //System.out.println(""+tv.getPaddingTop()+"/"+tv.getPaddingBottom());

    if(minViewHeight >0 && maxViewHeight >2)
    {
      Rect currentBounds = new Rect();
      tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
      //System.out.println(""+initSize);
      //System.out.println(""+maxViewHeight);
      //System.out.println(""+(currentBounds.height()));

      float resultingSize = 1;
      while(currentBounds.height() < maxViewHeight)
      {
        resultingSize ++;
        tv.setTextSize(resultingSize);

        tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
        //System.out.println(""+(currentBounds.height()+tv.getPaddingBottom()+tv.getPaddingTop()));
        //System.out.println("Resulting: "+resultingSize);
      }
      if(currentBounds.height()>=maxViewHeight)
      {
        //just to be sure, reduce the value
        tv.setTextSize(resultingSize-1);
      }
    }
}

我认为问题出在使用tv.getPaint().getTextBounds(...)上。它总是返回相对于tv.getWidth()tv.getHeight()值来说很小的文本界限值...即使文本大小远大于TextView的宽度或高度。


可能是重复的问题,请参考以下链接:https://dev59.com/s3E85IYBdhLWcg3wx2j2#7875656 - AlikElzin-kilaka
可能是自动缩放TextView文本以适应边界的重复问题。 - Peter Ritchie
这个解决方案很棒:如何缩放视图中的文本以适应? - Bob bobbington
14个回答

23

AutofitTextView库可以很好地处理此问题。源代码存储在Github上(1k+星):https://github.com/grantland/android-autofittextview

将以下内容添加到您的app/build.gradle文件中:

repositories {
    mavenCentral()
}

dependencies {
    implementation 'me.grantland:autofittextview:0.2.+'
}

在代码中启用任何扩展TextView的视图:

AutofitHelper.create(textView);

在XML中启用任何扩展TextView的视图:

<me.grantland.widget.AutofitLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        />
</me.grantland.widget.AutofitLayout>

可以在代码或XML中使用内置小部件:

<me.grantland.widget.AutofitTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:singleLine="true"
    />

1
谢谢,非常好的答案,我会为它点赞两次!我花了一些时间才明白TextViewtextSize属性现在被用作最大大小限制,所以你必须尽可能地指定它... - Mixaz
我喜欢这个库的外观,但它似乎并不真正支持StyleSpans,它们很可能会导致文本被截断。 - MathiasTCK
完美的解决方案!与iOS的adjustsFontSizeToFitWidth功能类似。 - Offek

12

3
如果您使用支持库:         app:autoSizeTextType="uniform"         app:autoSizeMinTextSize="12sp"         app:autoSizeMaxTextSize="100sp"         app:autoSizeStepGranularity="2sp" 通过使用:xmlns:app="http://schemas.android.com/apk/res-auto" - letroll

5
我已经花费了相当长的时间来尝试在各种7英寸平板电脑(如Kindle Fire、Nexus7以及中国一些低分辨率屏幕的廉价设备)上正确设置字体大小。最终对我有效的方法如下。其中的“32”是一个任意因素,基本上可以在7英寸平板电脑水平线上提供大约70多个字符,这是我正在寻找的字体大小。请根据需要进行调整。
textView.setTextSize(getFontSize(activity));


public static int getFontSize (Activity activity) { 

    DisplayMetrics dMetrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(dMetrics);

    // lets try to get them back a font size realtive to the pixel width of the screen
    final float WIDE = activity.getResources().getDisplayMetrics().widthPixels;
    int valueWide = (int)(WIDE / 32.0f / (dMetrics.scaledDensity));
    return valueWide;
}

感谢你的发布。这个代码完美运行,我将对其进行修改以接受输入的文本大小,然后输出正确的大小。 - a54studio
不错的建议改进,是的,您可以指定一个目标大小,然后它可以为所有设备设置通用大小。 - Mark JW

3
我能够使用以下代码回答我的问题(见下文),但我的解决方案非常特定于应用程序。例如,这只适用于大小约为屏幕的1/2的TextView(还具有40px的顶部边距和20px的侧边距...没有底部边距)。
然而,通过使用这种方法,您可以创建自己的类似实现。静态方法基本上只查看字符数并确定要应用于TextView文本大小的缩放因子,然后逐步增加文本大小,直到整体高度(使用文本宽度、文本高度和TextView宽度估计的高度)略低于TextView的高度。用于确定缩放因子的参数(即if / else if语句)是通过猜测和检查设置的。您可能需要尝试不同的数字才能使其适用于您的特定应用程序。
这不是最优雅的解决方案,但它很容易编码并且对我有效。是否有更好的方法?
public static void autoScaleTextViewTextToHeight(final TextView tv, String s)
    {       
        float currentWidth=tv.getPaint().measureText(s);
        int scalingFactor = 0;
        final int characters = s.length();
        //scale based on # of characters in the string
        if(characters<5)
        {
            scalingFactor = 1;
        }
        else if(characters>=5 && characters<10)
        {
            scalingFactor = 2;
        }
        else if(characters>=10 && characters<15)
        {
            scalingFactor = 3;
        }
        else if(characters>=15 && characters<20)
        {
            scalingFactor = 3;
        }
        else if(characters>=20 && characters<25)
        {
            scalingFactor = 3;
        }
        else if(characters>=25 && characters<30)
        {
            scalingFactor = 3;
        }
        else if(characters>=30 && characters<35)
        {
            scalingFactor = 3;
        }
        else if(characters>=35 && characters<40)
        {
            scalingFactor = 3;
        }
        else if(characters>=40 && characters<45)
        {
            scalingFactor = 3;
        }
        else if(characters>=45 && characters<50)
        {
            scalingFactor = 3;
        }
        else if(characters>=50 && characters<55)
        {
            scalingFactor = 3;
        }
        else if(characters>=55 && characters<60)
        {
            scalingFactor = 3;
        }
        else if(characters>=60 && characters<65)
        {
            scalingFactor = 3;
        }
        else if(characters>=65 && characters<70)
        {
            scalingFactor = 3;
        }
        else if(characters>=70 && characters<75)
        {
            scalingFactor = 3;
        }
        else if(characters>=75)
        {
            scalingFactor = 5;
        }

        //System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
        //the +scalingFactor is important... increase this if nec. later
        while((((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor)*tv.getTextSize())<tv.getHeight())
        {
            tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, tv.getTextSize()+0.25f);
            currentWidth=tv.getPaint().measureText(s);
            //System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
        }

        tv.setText(s);
    }

感谢您的选择。

21
为什么不把字符范围在15到75之间的内容压缩为一个语句? - Mark

3

在寻找解决方案时,我偶然发现了这个方法...... 我尝试了 Stack Overflow 等其他所有可见的解决方案,但都没有真正有效,因此我自己编写了一个解决方案。

基本上,通过将文本视图包装在自定义线性布局中,我已经成功地通过确保它使用固定宽度进行测量来正确地测量了文本。

<!-- TextView wrapped in the custom LinearLayout that expects one child TextView -->
<!-- This view should specify the size you would want the text view to be displayed at -->
<com.custom.ResizeView
    android:layout_width="fill_parent"
    android:layout_height="0dp"
    android:layout_margin="10dp"
    android:layout_weight="1"
    android:orientation="horizontal" >

    <TextView
        android:id="@+id/CustomTextView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
</com.custom.ResizeView>

然后是线性布局代码。
public class ResizeView extends LinearLayout {

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

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

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // oldWidth used as a fixed width when measuring the size of the text
        // view at different font sizes
        final int oldWidth = getMeasuredWidth() - getPaddingBottom() - getPaddingTop();
        final int oldHeight = getMeasuredHeight() - getPaddingLeft() - getPaddingRight();

        // Assume we only have one child and it is the text view to scale
        TextView textView = (TextView) getChildAt(0);

        // This is the maximum font size... we iterate down from this
        // I've specified the sizes in pixels, but sp can be used, just modify
        // the call to setTextSize

        float size = getResources().getDimensionPixelSize(R.dimen.solutions_view_max_font_size);

        for (int textViewHeight = Integer.MAX_VALUE; textViewHeight > oldHeight; size -= 0.1f) {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);

            // measure the text views size using a fixed width and an
            // unspecified height - the unspecified height means measure
            // returns the textviews ideal height
            textView.measure(MeasureSpec.makeMeasureSpec(oldWidth, MeasureSpec.EXACTLY), MeasureSpec.UNSPECIFIED);

            textViewHeight = textView.getMeasuredHeight();
        }
    }
}

希望这能帮到某些人。

3
我遇到了同样的问题,写了一个类似可以解决这个问题的类。基本上,我使用了静态布局在单独的画布中绘制文本,并重新测量直到找到适合的字体大小。您可以在下面的主题中查看发布的类。希望对您有所帮助。 自动缩放TextView文本以适应边界

2

在测量文本之前,可以尝试将setHoriztonallyScrolling()设置为true,这样textView就不会尝试在多行上布局您的文本。


1
谢谢,但我希望文本可以显示在多行上。我只需要所有行都保持在textview的边界内,并尽可能大。 - RyanM

2
一种方法是为每个广义屏幕大小指定不同的sp尺寸。例如,为小屏幕提供8sp,正常屏幕提供12sp,大屏幕提供16sp,xlarge提供20sp。然后只需让您的布局引用@dimen文本大小或其他内容,您可以放心,因为密度通过sp单位进行处理。有关此方法的更多信息,请参见以下链接。

http://www.developer.android.com/guide/topics/resources/more-resources.html#Dimension

我必须指出,支持更多的语言意味着在测试阶段需要更多的工作,特别是如果您希望保持文本在一行上,因为某些语言有更长的单词。在这种情况下,例如在values-de-large文件夹中创建一个dimens.xml文件,并手动调整值。希望这可以帮到您。

开发您的母语(或将要演示的第一种语言)的工作模型,并在成功测试后切换对应语言以支持多语言。 - gh.

1
我创建了一个解决方案,基于其他反馈。这个解决方案允许您在XML中设置文本的大小,这将是最大尺寸,并且它会自动调整以适应视图高度。 可调整大小的TextView
 private float findNewTextSize(int width, int height, CharSequence text) {
            TextPaint textPaint = new TextPaint(getPaint());

            float targetTextSize = textPaint.getTextSize();

            int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            while(textHeight > height && targetTextSize > mMinTextSize) {
                    targetTextSize = Math.max(targetTextSize - 1, mMinTextSize);
                    textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            }
            return targetTextSize;
    }
private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
            paint.setTextSize(textSize);
            StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
            return layout.getHeight();
    }

0

这是基于mattmook的答案。在某些设备上表现良好,但不是所有设备都如此。我将调整大小移至测量步骤,使用自定义属性设置最大字体大小,考虑边距,并扩展FrameLayout而不是LineairLayout。

 public class ResizeView extends FrameLayout {
    protected float max_font_size;

    public ResizeView(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.ResizeView,
                0, 0);
        max_font_size = a.getDimension(R.styleable.ResizeView_maxFontSize, 30.0f);
    }

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

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        // Use the parent's code for the first measure
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Assume we only have one child and it is the text view to scale
        final TextView textView = (TextView) getChildAt(0);

        // Check if the default measure resulted in a fitting textView
        LayoutParams childLayout = (LayoutParams) textView.getLayoutParams();
        final int textHeightAvailable = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - childLayout.topMargin - childLayout.bottomMargin;
        int textViewHeight = textView.getMeasuredHeight();
        if (textViewHeight < textHeightAvailable) {
            return;
        }

        final int textWidthSpec = MeasureSpec.makeMeasureSpec(
                MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight() - childLayout.leftMargin - childLayout.rightMargin, 
                MeasureSpec.EXACTLY);
        final int textHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

        for (float size = max_font_size; size >= 1.05f; size-=0.1f) {
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
            textView.measure(textWidthSpec, textHeightSpec);

            textViewHeight = textView.getMeasuredHeight();
            if (textViewHeight <= textHeightAvailable) {
                break;
            }
        }
    }
}

而这个在attrs.xml中:

<declare-styleable name="ResizeView">
    <attr name="maxFontSize" format="reference|dimension"/>
</declare-styleable>

最后像这样使用:

<PACKAGE_NAME.ui.ResizeView xmlns:custom="http://schemas.android.com/apk/res/PACKAGE_NAME"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="start|center_vertical"
    android:padding="5dp"
    custom:maxFontSize="@dimen/normal_text">

    <TextView android:id="@+id/tabTitle2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</PACKAGE_NAME.ui.ResizeView>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接