背景

很多时候,我们需要自动适应TextView的字体给它的边界。

这个问题

遗憾的是,尽管有许多帖子和帖子(以及建议的解决方案)讨论这个问题(例如这里,这里和这里),但实际上没有一个能很好地工作。

这就是为什么,我决定测试他们,直到我找到真正的交易。

我认为这样一个textView的要求应该是:

Should allow using any font, typeface, style, and set of characters. Should handle both width and height No truncation unless text cannot fit because of the limitation, we've given to it (example: too long text, too small available size). However, we could request for horizontal/vertical scrollbar if we wish, just for those cases. Should allow multi-line or single-line. In case of multi-line, allow max & min lines. Should not be slow in computation. Using a loop for finding the best size? At least optimize it and don't increment your sampling by 1 each time. In case of multi-line, should allow to prefer resizing or using more lines, and/or allow to choose the lines ourselves by using the "\n" character.

我的努力

我尝试了很多样例(包括我写过的那些链接),我也试图修改它们来处理我所说的情况,但没有一个真正有效。

我已经做了一个示例项目,让我可以直观地看到TextView是否自动适配正确。

目前,我的示例项目只随机文本(英语字母加数字)和textView的大小,并让它保持单行,但即使这在我尝试过的任何示例上都不能很好地工作。

下面是代码(也可以在这里找到):

res / layout / activity_main.xml文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  android:layout_height="match_parent" tools:context=".MainActivity">
  <Button android:id="@+id/button1" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true" android:text="Button" />
  <FrameLayout android:layout_width="match_parent"
    android:layout_height="wrap_content" android:layout_above="@+id/button1"
    android:layout_alignParentLeft="true" android:background="#ffff0000"
    android:layout_alignParentRight="true" android:id="@+id/container"
    android:layout_alignParentTop="true" />

</RelativeLayout>

src /…/ MainActivity.java文件

public class MainActivity extends Activity
  {
  private final Random        _random            =new Random();
  private static final String ALLOWED_CHARACTERS ="qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";

  @Override
  protected void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final ViewGroup container=(ViewGroup)findViewById(R.id.container);
    findViewById(R.id.button1).setOnClickListener(new OnClickListener()
      {
        @Override
        public void onClick(final View v)
          {
          container.removeAllViews();
          final int maxWidth=container.getWidth();
          final int maxHeight=container.getHeight();
          final FontFitTextView fontFitTextView=new FontFitTextView(MainActivity.this);
          final int width=_random.nextInt(maxWidth)+1;
          final int height=_random.nextInt(maxHeight)+1;
          fontFitTextView.setLayoutParams(new LayoutParams(width,height));
          fontFitTextView.setSingleLine();
          fontFitTextView.setBackgroundColor(0xff00ff00);
          final String text=getRandomText();
          fontFitTextView.setText(text);
          container.addView(fontFitTextView);
          Log.d("DEBUG","width:"+width+" height:"+height+" text:"+text);
          }
      });
    }

  private String getRandomText()
    {
    final int textLength=_random.nextInt(20)+1;
    final StringBuilder builder=new StringBuilder();
    for(int i=0;i<textLength;++i)
      builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
    return builder.toString();
    }
  }

这个问题

有人知道这个常见问题的有效解决方案吗?

即使一个解决方案的功能比我所写的要少得多,例如,一个解决方案只有固定的文本行数,并根据其大小调整字体,但绝不会出现奇怪的故障,也不会让文本与可用空间相比变得太大或太小。


GitHub项目

由于这是一个如此重要的TextView,我决定发布一个库,这样每个人都可以轻松地使用它,并在这里为它做出贡献。


当前回答

如果你正在寻找更简单的方法:

 public MyTextView extends TextView{

    public void resize(String text, float textViewWidth, float textViewHeight) {
       Paint p = new Paint();
       Rect bounds = new Rect();
       p.setTextSize(1);
       p.getTextBounds(text, 0, text.length(), bounds);
       float widthDifference = (textViewWidth)/bounds.width();
       float heightDifference = (textViewHeight);
       textSize = Math.min(widthDifference, heightDifference);
       setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

其他回答

我对M-WaJeEh的回答做了一些修改,以考虑到两边的复合提款。

getCompoundPaddingXXXX()方法返回视图的填充+可绘制空间。例如:getcompoundpaddingleft ()

问题: 这修正了文本可用的TextView空间的宽度和高度的测量。如果我们不考虑可绘制对象的大小,它就会被忽略,文本最终会与可绘制对象重叠。


更新段adjustTextSize(String):

private void adjustTextSize(final String text) {
  if (!mInitialized) {
    return;
  }
  int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
  mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();

  mAvailableSpaceRect.right = mWidthLimit;
  mAvailableSpaceRect.bottom = heightLimit;

  int maxTextSplits = text.split(" ").length;
  AutoResizeTextView.super.setMaxLines(Math.min(maxTextSplits, mMaxLines));

  super.setTextSize(
      TypedValue.COMPLEX_UNIT_PX,
      binarySearch((int) mMinTextSize, (int) mMaxTextSize,
                   mSizeTester, mAvailableSpaceRect));
}

如果你正在寻找更简单的方法:

 public MyTextView extends TextView{

    public void resize(String text, float textViewWidth, float textViewHeight) {
       Paint p = new Paint();
       Rect bounds = new Rect();
       p.setTextSize(1);
       p.getTextBounds(text, 0, text.length(), bounds);
       float widthDifference = (textViewWidth)/bounds.width();
       float heightDifference = (textViewHeight);
       textSize = Math.min(widthDifference, heightDifference);
       setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

现在这个问题有了官方的解决方案。Android O引入的自动调整TextViews在支持库26中可用,并向后兼容一直到Android 4.0。

https://developer.android.com/preview/features/autosizing-textview.html

我不知道为什么包括这个信息的https://stackoverflow.com/a/42940171/47680被管理员删除了。

在我尝试了Android官方自动调整TextView后,我发现如果你的Android版本是Android 8.0 (API级别26)之前,你需要使用Android .support.v7.widget。AppCompatTextView,并确保支持库版本高于26.0.0。例子:

<android.support.v7.widget.AppCompatTextView
    android:layout_width="130dp"
    android:layout_height="32dp"
    android:maxLines="1"
    app:autoSizeMaxTextSize="22sp"
    app:autoSizeMinTextSize="12sp"
    app:autoSizeStepGranularity="2sp"
    app:autoSizeTextType="uniform" />

更新:

根据@android-developer的回复,我检查了AppCompatActivity源代码,并在onCreate中发现了这两行

final AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory();

和在AppCompatDelegateImpl的createView中

    if (mAppCompatViewInflater == null) {
        mAppCompatViewInflater = new AppCompatViewInflater();
    }

它使用AppCompatViewInflater膨胀器视图,当AppCompatViewInflater createView它将使用AppCompatTextView为“TextView”。

public final View createView(){
    ...
    View view = null;
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
            break;
        case "Button":
            view = new AppCompatButton(context, attrs);
            break;
    ...
}

在我的项目中我不使用AppCompatActivity,所以我需要使用<android.support.v7.widget。AppCompatTextView>的xml格式。

试试这个

TextWatcher changeText = new TextWatcher() {
     @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                tv3.setText(et.getText().toString());
                tv3.post(new Runnable() {           
                    @Override
                    public void run() {
                    while(tv3.getLineCount() >= 3){                     
                            tv3.setTextSize((tv3.getTextSize())-1);                     
                        }
                    }
                });
            }

            @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

            @Override public void afterTextChanged(Editable s) { }
        };