背景

很多时候,我们需要自动适应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));
}

将文本视图转换为图像,并在边界内缩放图像。

这是一个关于如何将视图转换为图像的例子:转换视图到位图而不显示它在Android?

问题是,你的文本将是不可选择的,但它应该做到这一点。我还没有尝试过,所以我不确定它会是什么样子(因为缩放)。

我将逐步解释这个属性在低版本的android中是如何工作的:

1-导入android支持库26.x。X在你的项目gradle文件。如果IDE上没有支持库,它们将自动下载。

dependencies {
    compile 'com.android.support:support-v4:26.1.0'
    compile 'com.android.support:appcompat-v7:26.1.0'
    compile 'com.android.support:support-v13:26.1.0' }

allprojects {
    repositories {
        jcenter()
        maven {
            url "https://maven.google.com"
        }
    } }

2-打开你的布局XML文件和重构像这个标签你的TextView。这种情况是:当在系统上增加字体大小时,将文本调整为可用宽度,而不是换行。

<android.support.v7.widget.AppCompatTextView
            android:id="@+id/textViewAutoSize"
            android:layout_width="match_parent"
            android:layout_height="25dp"
            android:ellipsize="none"
            android:text="Auto size text with compatible lower android versions."
            android:textSize="12sp"
            app:autoSizeMaxTextSize="14sp"
            app:autoSizeMinTextSize="4sp"
            app:autoSizeStepGranularity="0.5sp"
            app:autoSizeTextType="uniform" />

快速修复了@Malachiasz描述的问题

我已经通过在自动调整大小类中添加自定义支持来修复这个问题:

public void setTextCompat(final CharSequence text) {
    setTextCompat(text, BufferType.NORMAL);
}

public void setTextCompat(final CharSequence text, BufferType type) {
    // Quick fix for Android Honeycomb and Ice Cream Sandwich which sets the text only on the first call
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        super.setText(DOUBLE_BYTE_WORDJOINER + text + DOUBLE_BYTE_WORDJOINER, type);
    } else {
        super.setText(text, type);
    }
}

@Override
public CharSequence getText() {
    String originalText = super.getText().toString();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 &&
        Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
        // We try to remove the word joiners we added using compat method - if none found - this will do nothing.
        return originalText.replaceAll(DOUBLE_BYTE_WORDJOINER, "");
    } else {
        return originalText;
    }
}

只需调用yourView.setTextCompat(newTextValue)而不是yourView.setText(newTextValue)

好吧,我已经用了上周大量重写我的代码,以精确地适应您的测试。现在你可以1:1复制它,它将立即工作——包括setSingleLine()。请记住调整MIN_TEXT_SIZE和MAX_TEXT_SIZE,如果你要去的极端值。

收敛算法是这样的:

for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {

    // Go to the mean value...
    testSize = (upperTextSize + lowerTextSize) / 2;

    // ... inflate the dummy TextView by setting a scaled textSize and the text...
    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
    mTestView.setText(text);

    // ... call measure to find the current values that the text WANTS to occupy
    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    int tempHeight = mTestView.getMeasuredHeight();

    // ... decide whether those values are appropriate.
    if (tempHeight >= targetFieldHeight) {
        upperTextSize = testSize; // Font is too big, decrease upperSize
    }
    else {
        lowerTextSize = testSize; // Font is too small, increase lowerSize
    }
}

整个班级都可以在这里找到。

现在的结果非常灵活。这与在xml中声明的是一样的:

<com.example.myProject.AutoFitText
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="4"
    android:text="@string/LoremIpsum" />

... 以及像您的测试一样以编程方式构建。

我真的希望你现在能用上这个。你可以调用setText(CharSequence文本)现在顺便使用它。这个类会处理非常罕见的异常,应该是坚如磐石的。该算法目前唯一不支持的是:

调用setMaxLines(x),其中x >= 2

但是我已经添加了大量的评论来帮助你建立这个,如果你想!


请注意:

If you just use this normally without limiting it to a single line then there might be word-breaking as you mentioned before. This is an Android feature, not the fault of the AutoFitText. Android will always break words that are too long for a TextView and it's actually quite a convenience. If you want to intervene here than please see my comments and code starting at line 203. I have already written an adequate split and the recognition for you, all you'd need to do henceforth is to devide the words and then modify as you wish.

总之:你应该认真考虑重写你的测试以支持空格字符,如下所示:

final Random _random = new Random();
final String ALLOWED_CHARACTERS = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
final int textLength = _random.nextInt(80) + 20;
final StringBuilder builder = new StringBuilder();
for (int i = 0; i < textLength; ++i) {
    if (i % 7 == 0 && i != 0) {
        builder.append(" ");
    }
    builder.append(ALLOWED_CHARACTERS.charAt(_random.nextInt(ALLOWED_CHARACTERS.length())));
}
((AutoFitText) findViewById(R.id.textViewMessage)).setText(builder.toString());

这将产生非常漂亮(和更现实)的结果。 你会发现评论也会让你开始这件事。

祝你好运,并致以最良好的问候