首页 文章

自动适应TextView for Android

提问于
浏览
222

背景

很多时候,我们需要将TextView的字体自动调整到给定的边界 .

问题

可悲的是,即使有很多线程和帖子(和建议的解决方案)谈论这个问题(例如hereherehere),但它们都没有真正奏效 .

这就是为什么,我决定测试每一个,直到找到真正的交易 .

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

  • 应允许使用任何字体,字体,样式和字符集 .

  • 应处理宽度和高度

  • 没有截断,除非文本由于限制而无法适应,我们已经给它(例如:文本太长,可用尺寸太小) . 但是,如果我们愿意,我们可以请求水平/垂直滚动条,仅适用于那些情况 .

  • 应允许多行或单行 . 如果是多行,请允许最大和最小行 .

  • 计算不应该慢 . 使用循环找到最佳尺寸?至少要优化它,不要每次增加1个采样 .

  • 如果是多行,应该允许更喜欢调整大小或使用更多行,和/或允许使用“\ n”字符自己选择行 .

我尝试过的

我已经尝试过这么多样本(包括链接的那些,我已经写过了),而且我也尝试修改它们来处理这些情况,我已经谈过了,但没有一个真正起作用 .

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

目前,我的示例项目只是随机化文本(英文字母加数字)和textView的大小,并让它保持单行,但即使这对我尝试过的任何样本都不起作用 .

这是代码(也可用here):

文件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,我决定发布一个库,这样每个人都可以轻松地使用它,并为它做出贡献,here .

15 回答

  • 140

    感谢MartinH的简单修复here,此代码还处理 android:drawableLeftandroid:drawableRightandroid:drawableTopandroid:drawableBottom 标记 .


    我的回答应该会让你开心Auto Scale TextView Text to Fit within Bounds

    我修改了你的测试用例:

    @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 AutoResizeTextView fontFitTextView = new AutoResizeTextView(MainActivity.this);
                final int width = _random.nextInt(maxWidth) + 1;
                final int height = _random.nextInt(maxHeight) + 1;
                fontFitTextView.setLayoutParams(new FrameLayout.LayoutParams(
                        width, height));
                int maxLines = _random.nextInt(4) + 1;
                fontFitTextView.setMaxLines(maxLines);
                fontFitTextView.setTextSize(500);// max size
                fontFitTextView.enableSizeCache(false);
                fontFitTextView.setBackgroundColor(0xff00ff00);
                final String text = getRandomText();
                fontFitTextView.setText(text);
                container.addView(fontFitTextView);
                Log.d("DEBUG", "width:" + width + " height:" + height
                        + " text:" + text + " maxLines:" + maxLines);
            }
        });
    }
    

    我在这里根据android开发人员的请求发布代码:

    Final effect:

    Enter image description here

    Sample Layout file:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp" >
    
    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:maxLines="2"
        android:text="Auto Resized Text, max 2 lines"
        android:textSize="100sp" /> <!-- maximum size -->
    
    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:ellipsize="none"
        android:gravity="center"
        android:maxLines="1"
        android:text="Auto Resized Text, max 1 line"
        android:textSize="100sp" /> <!-- maximum size -->
    
    <com.vj.widgets.AutoResizeTextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Auto Resized Text"
        android:textSize="500sp" /> <!-- maximum size -->
    
    </LinearLayout>
    

    And the Java code:

    import android.annotation.TargetApi;
    import android.content.Context;
    import android.content.res.Resources;
    import android.graphics.RectF;
    import android.os.Build;
    import android.text.Layout.Alignment;
    import android.text.StaticLayout;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.SparseIntArray;
    import android.util.TypedValue;
    import android.widget.TextView;
    
    public class AutoResizeTextView extends TextView {
        private interface SizeTester {
            /**
             *
             * @param suggestedSize
             *            Size of text to be tested
             * @param availableSpace
             *            available space in which text must fit
             * @return an integer < 0 if after applying {@code suggestedSize} to
             *         text, it takes less space than {@code availableSpace}, > 0
             *         otherwise
             */
            public int onTestSize(int suggestedSize, RectF availableSpace);
        }
    
        private RectF mTextRect = new RectF();
    
        private RectF mAvailableSpaceRect;
    
        private SparseIntArray mTextCachedSizes;
    
        private TextPaint mPaint;
    
        private float mMaxTextSize;
    
        private float mSpacingMult = 1.0f;
    
        private float mSpacingAdd = 0.0f;
    
        private float mMinTextSize = 20;
    
        private int mWidthLimit;
    
        private static final int NO_LINE_LIMIT = -1;
        private int mMaxLines;
    
        private boolean mEnableSizeCache = true;
        private boolean mInitializedDimens;
    
        public AutoResizeTextView(Context context) {
            super(context);
            initialize();
        }
    
        public AutoResizeTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initialize();
        }
    
        public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            initialize();
        }
    
        private void initialize() {
            mPaint = new TextPaint(getPaint());
            mMaxTextSize = getTextSize();
            mAvailableSpaceRect = new RectF();
            mTextCachedSizes = new SparseIntArray();
            if (mMaxLines == 0) {
                // no value was assigned during construction
                mMaxLines = NO_LINE_LIMIT;
            }
        }
    
        @Override
        public void setTextSize(float size) {
            mMaxTextSize = size;
            mTextCachedSizes.clear();
            adjustTextSize();
        }
    
        @Override
        public void setMaxLines(int maxlines) {
            super.setMaxLines(maxlines);
            mMaxLines = maxlines;
            adjustTextSize();
        }
    
        public int getMaxLines() {
            return mMaxLines;
        }
    
        @Override
        public void setSingleLine() {
            super.setSingleLine();
            mMaxLines = 1;
            adjustTextSize();
        }
    
        @Override
        public void setSingleLine(boolean singleLine) {
            super.setSingleLine(singleLine);
            if (singleLine) {
                mMaxLines = 1;
            } else {
                mMaxLines = NO_LINE_LIMIT;
            }
            adjustTextSize();
        }
    
        @Override
        public void setLines(int lines) {
            super.setLines(lines);
            mMaxLines = lines;
            adjustTextSize();
        }
    
        @Override
        public void setTextSize(int unit, float size) {
            Context c = getContext();
            Resources r;
    
            if (c == null)
                r = Resources.getSystem();
            else
                r = c.getResources();
            mMaxTextSize = TypedValue.applyDimension(unit, size,
                    r.getDisplayMetrics());
            mTextCachedSizes.clear();
            adjustTextSize();
        }
    
        @Override
        public void setLineSpacing(float add, float mult) {
            super.setLineSpacing(add, mult);
            mSpacingMult = mult;
            mSpacingAdd = add;
        }
    
        /**
         * Set the lower text size limit and invalidate the view
         *
         * @param minTextSize
         */
        public void setMinTextSize(float minTextSize) {
            mMinTextSize = minTextSize;
            adjustTextSize();
        }
    
        private void adjustTextSize() {
            if (!mInitializedDimens) {
                return;
            }
            int startSize = (int) mMinTextSize;
            int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
                    - getCompoundPaddingTop();
            mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            mAvailableSpaceRect.right = mWidthLimit;
            mAvailableSpaceRect.bottom = heightLimit;
            super.setTextSize(
                    TypedValue.COMPLEX_UNIT_PX,
                    efficientTextSizeSearch(startSize, (int) mMaxTextSize,
                            mSizeTester, mAvailableSpaceRect));
        }
    
        private final SizeTester mSizeTester = new SizeTester() {
            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
            @Override
            public int onTestSize(int suggestedSize, RectF availableSPace) {
                mPaint.setTextSize(suggestedSize);
                String text = getText().toString();
                boolean singleline = getMaxLines() == 1;
                if (singleline) {
                    mTextRect.bottom = mPaint.getFontSpacing();
                    mTextRect.right = mPaint.measureText(text);
                } else {
                    StaticLayout layout = new StaticLayout(text, mPaint,
                            mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
                            mSpacingAdd, true);
    
                    // Return early if we have more lines
                    if (getMaxLines() != NO_LINE_LIMIT
                            && layout.getLineCount() > getMaxLines()) {
                        return 1;
                    }
                    mTextRect.bottom = layout.getHeight();
                    int maxWidth = -1;
                    for (int i = 0; i < layout.getLineCount(); i++) {
                        if (maxWidth < layout.getLineWidth(i)) {
                            maxWidth = (int) layout.getLineWidth(i);
                        }
                    }
                    mTextRect.right = maxWidth;
                }
    
                mTextRect.offsetTo(0, 0);
                if (availableSPace.contains(mTextRect)) {
    
                    // May be too small, don't worry we will find the best match
                    return -1;
                } else {
                    // too big
                    return 1;
                }
            }
        };
    
        /**
         * Enables or disables size caching, enabling it will improve performance
         * where you are animating a value inside TextView. This stores the font
         * size against getText().length() Be careful though while enabling it as 0
         * takes more space than 1 on some fonts and so on.
         *
         * @param enable
         *            Enable font size caching
         */
        public void enableSizeCache(boolean enable) {
            mEnableSizeCache = enable;
            mTextCachedSizes.clear();
            adjustTextSize(getText().toString());
        }
    
        private int efficientTextSizeSearch(int start, int end,
                SizeTester sizeTester, RectF availableSpace) {
            if (!mEnableSizeCache) {
                return binarySearch(start, end, sizeTester, availableSpace);
            }
            int key = getText().toString().length();
            int size = mTextCachedSizes.get(key);
            if (size != 0) {
                return size;
            }
            size = binarySearch(start, end, sizeTester, availableSpace);
            mTextCachedSizes.put(key, size);
            return size;
        }
    
        private static int binarySearch(int start, int end, SizeTester sizeTester,
                RectF availableSpace) {
            int lastBest = start;
            int lo = start;
            int hi = end - 1;
            int mid = 0;
            while (lo <= hi) {
                mid = (lo + hi) >>> 1;
                int midValCmp = sizeTester.onTestSize(mid, availableSpace);
                if (midValCmp < 0) {
                    lastBest = lo;
                    lo = mid + 1;
                } else if (midValCmp > 0) {
                    hi = mid - 1;
                    lastBest = hi;
                } else {
                    return mid;
                }
            }
            // Make sure to return the last best.
            // This is what should always be returned.
            return lastBest;
    
        }
    
        @Override
        protected void onTextChanged(final CharSequence text, final int start,
                final int before, final int after) {
            super.onTextChanged(text, start, before, after);
            adjustTextSize();
        }
    
        @Override
        protected void onSizeChanged(int width, int height, int oldwidth,
                int oldheight) {
            mInitializedDimens = true;
            mTextCachedSizes.clear();
            super.onSizeChanged(width, height, oldwidth, oldheight);
            if (width != oldwidth || height != oldheight) {
                adjustTextSize();
            }
        }
    }
    

    Warning:

    请注意Android 3.1(Honeycomb)中的这个resolved bug .

  • 4

    我已经修改了M-WaJeEh的答案,以考虑侧面的复合抽屉 .

    getCompoundPaddingXXXX() 方法返回 padding of the view + drawable space . 参见例如:TextView.getCompoundPaddingLeft()

    Issue: 这修复了可用于文本的TextView空间的宽度和高度的度量 . 如果我们不考虑可绘制的大小,它将被忽略,文本将最终与drawable重叠 .


    Updated segment 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));
    }
    
  • 3

    好的,我上周使用了大量重写我的代码,以适合您的测试 . 您现在可以复制1:1,它将立即起作用 - 包括 setSingleLine() . 如果你想要极端值,请记得调整 MIN_TEXT_SIZEMAX_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
        }
    }
    

    全班可以be found here.

    结果现在非常灵活 . 这与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 text) 来使用它 . 该课程照顾了极少数的例外,应该是坚如磐石的 . 该算法唯一支持的是:

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

    但是如果你愿意的话,我已经添加了大量的评论来帮助你构建它!


    Please note:

    如果您只是正常使用它而不将其限制为单行,那么可能会出现如前所述的破字 . 这是 an Android featurenot AutoFitText 的错误 . Android总是会破坏TextView太长的单词,而今后需要做的就是将单词分开,然后根据需要进行修改 .

    总结:您应该高度考虑重写您的测试以支持空间字符,如下所示:

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

    这将产生非常美妙(和更现实)的结果 .
    你会发现评论也可以帮助你开始这个问题 .

    祝你好运和最好的问候

  • 1

    我的要求是

    • 单击ScalableTextView

    • 打开listActivity并显示各种长度的字符串项 .

    • 选择一个列表中的文字 .

    • 将文本设置回另一个活动中的ScalableTextView .

    我提到了链接:Auto Scale TextView Text to Fit within Bounds(包括评论)和DialogTitle.java

    我发现提供的解决方案很简单但不会动态地改变文本框的大小 . 当列表视图中的选定文本长度大于 ScalableTextView 中的现有文本长度时,它非常有用 . 当选择长度小于 ScalableTextView 中现有文本的文本时,它不会增加文本的大小,显示较小的文本 .

    我修改了ScalableTextView.java以根据文本长度重新调整文本大小 . 这是我的 ScalableTextView.java

    public class ScalableTextView extends TextView
    {
    float defaultTextSize = 0.0f;
    
    public ScalableTextView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        setSingleLine();
        setEllipsize(TruncateAt.END);
        defaultTextSize = getTextSize();
    }
    
    public ScalableTextView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        setSingleLine();
        setEllipsize(TruncateAt.END);
        defaultTextSize = getTextSize();
    }
    
    public ScalableTextView(Context context)
    {
        super(context);
        setSingleLine();
        setEllipsize(TruncateAt.END);
        defaultTextSize = getTextSize();
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
        final Layout layout = getLayout();
        if (layout != null)
        {
            final int lineCount = layout.getLineCount();
            if (lineCount > 0)
            {
                int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
                while (ellipsisCount > 0)
                {
                    final float textSize = getTextSize();
    
                    // textSize is already expressed in pixels
                    setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
    
                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                    ellipsisCount = layout.getEllipsisCount(lineCount - 1);
                }
            }
        }
    }
    }
    

    快乐编码....

  • 0

    Warning, bug in Android 3 (Honeycomb) and Android 4.0 (Ice Cream Sandwich)

    机器人版本:3.1 - 4.04有一个bug,即TextView中的setTextSize()仅在第一次工作时(第一次调用) .

    该错误在Issue 22493: TextView height bug in Android 4.0Issue 17343: button's height and text does not return to its original state after increase and decrease the text size on HoneyComb中描述 .

    解决方法是在更改大小之前向分配给TextView的文本添加换行符:

    final String DOUBLE_BYTE_SPACE = "\u3000";
    textView.append(DOUBLE_BYTE_SPACE);
    

    我在我的代码中使用它如下:

    final String DOUBLE_BYTE_SPACE = "\u3000";
    AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
    String fixString = "";
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1
       && android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {  
        fixString = DOUBLE_BYTE_SPACE;
    }
    textView.setText(fixString + "The text" + fixString);
    

    我在文本的左侧和右侧添加了这个“\ u3000”字符,以使其保持居中 . 如果您将其对齐到左侧,则仅附加到右侧 . 当然它也可以嵌入AutoResizeTextView小部件,但我想将修复代码保留在外面 .

  • 2

    现在已经有了这个问题的官方解决方案 . 使用Android O自动调整TextViews可在支持库26中获得,并且向后兼容到Android 4.0 .

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

    我不确定为什么包含此信息的https://stackoverflow.com/a/42940171/47680已被管理员删除 .

  • 7

    我将解释这个属性是如何逐步降低android版本的:

    1-在项目gradle文件中导入android支持库26.x.x.如果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" />
    
  • 0

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

    以下是有关如何将视图转换为图像的示例:Converting a view to Bitmap without displaying it in Android?

    问题是,你的文字将无法选择,但它应该可以解决问题 . 我没有尝试过,所以我不确定它的外观(因为缩放) .

  • 14

    从2018年6月起Android正式支持this featureAndroid 4.0 (API level 14) and higher.
    使用Android 8.0(API级别26)及更高版本:

    setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, 
            int autoSizeStepGranularity, int unit);
    

    Android 8.0之前的Android版本(API级别26):

    TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(TextView textView,
    int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit)
    

    查看我的详细信息answer .

  • 0

    下面是avalancha TextView,增加了自定义Font的功能 .

    用法:

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:foo="http://schemas.android.com/apk/res-auto"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" >
    
                    <de.meinprospekt.androidhd.view.AutoFitText
                    android:layout_width="wrap_content"
                    android:layout_height="10dp"
                    android:text="Small Text"
                    android:textColor="#FFFFFF"
                    android:textSize="100sp"
                    foo:customFont="fonts/Roboto-Light.ttf" />
    
    </FrameLayout>
    

    不要忘记添加:xmlns:foo =“http://schemas.android.com/apk/res-auto” . 字体应该在资产中

    import java.util.ArrayList;
    import java.util.List;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Paint;
    import android.graphics.Typeface;
    import android.os.Build;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.util.TypedValue;
    import android.view.View;
    import android.view.ViewGroup.LayoutParams;
    import android.view.ViewTreeObserver;
    import android.view.ViewTreeObserver.OnGlobalLayoutListener;
    import android.widget.TextView;
    import de.meinprospekt.androidhd.R;
    import de.meinprospekt.androidhd.adapter.BrochuresHorizontalAdapter;
    import de.meinprospekt.androidhd.util.LOG;
    
    /**
     * https://stackoverflow.com/a/16174468/2075875 This class builds a new android Widget named AutoFitText which can be used instead of a TextView to
     * have the text font size in it automatically fit to match the screen width. Credits go largely to Dunni, gjpc, gregm and speedplane from
     * Stackoverflow, method has been (style-) optimized and rewritten to match android coding standards and our MBC. This version upgrades the original
     * "AutoFitTextView" to now also be adaptable to height and to accept the different TextView types (Button, TextClock etc.)
     * 
     * @author pheuschk
     * @createDate: 18.04.2013
     * 
     * combined with: https://stackoverflow.com/a/7197867/2075875
     */
    @SuppressWarnings("unused")
    public class AutoFitText extends TextView {
    
        private static final String TAG = AutoFitText.class.getSimpleName();
    
        /** Global min and max for text size. Remember: values are in pixels! */
        private final int MIN_TEXT_SIZE = 10;
        private final int MAX_TEXT_SIZE = 400;
    
        /** Flag for singleLine */
        private boolean mSingleLine = false;
    
        /**
         * A dummy {@link TextView} to test the text size without actually showing anything to the user
         */
        private TextView mTestView;
    
        /**
         * A dummy {@link Paint} to test the text size without actually showing anything to the user
         */
        private Paint mTestPaint;
    
        /**
         * Scaling factor for fonts. It's a method of calculating independently (!) from the actual density of the screen that is used so users have the
         * same experience on different devices. We will use DisplayMetrics in the Constructor to get the value of the factor and then calculate SP from
         * pixel values
         */
        private float mScaledDensityFactor;
    
        /**
         * Defines how close we want to be to the factual size of the Text-field. Lower values mean higher precision but also exponentially higher
         * computing cost (more loop runs)
         */
        private final float mThreshold = 0.5f;
    
        /**
         * Constructor for call without attributes --> invoke constructor with AttributeSet null
         * 
         * @param context
         */
        public AutoFitText(Context context) {
            this(context, null);
        }
    
        public AutoFitText(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context, attrs);
        }
    
        public AutoFitText(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(context, attrs);
        }
    
        private void init(Context context, AttributeSet attrs) {
            //TextViewPlus part https://stackoverflow.com/a/7197867/2075875
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AutoFitText);
            String customFont = a.getString(R.styleable.AutoFitText_customFont);
            setCustomFont(context, customFont);
            a.recycle();
    
            // AutoFitText part
            mScaledDensityFactor = context.getResources().getDisplayMetrics().scaledDensity;
            mTestView = new TextView(context);
    
            mTestPaint = new Paint();
            mTestPaint.set(this.getPaint());
    
            this.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    
                @Override
                public void onGlobalLayout() {
                    // make an initial call to onSizeChanged to make sure that refitText is triggered
                    onSizeChanged(AutoFitText.this.getWidth(), AutoFitText.this.getHeight(), 0, 0);
                    // Remove the LayoutListener immediately so we don't run into an infinite loop
                    //AutoFitText.this.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    removeOnGlobalLayoutListener(AutoFitText.this, this);
                }
            });
        }
    
        public boolean setCustomFont(Context ctx, String asset) {
            Typeface tf = null;
            try {
            tf = Typeface.createFromAsset(ctx.getAssets(), asset);  
            } catch (Exception e) {
                LOG.e(TAG, "Could not get typeface: "+e.getMessage());
                return false;
            }
    
            setTypeface(tf);  
            return true;
        }
    
        @SuppressLint("NewApi")
        public static void removeOnGlobalLayoutListener(View v, ViewTreeObserver.OnGlobalLayoutListener listener){
            if (Build.VERSION.SDK_INT < 16) {
                v.getViewTreeObserver().removeGlobalOnLayoutListener(listener);
            } else {
                v.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
            }
        }
    
        /**
         * Main method of this widget. Resizes the font so the specified text fits in the text box assuming the text box has the specified width. This is
         * done via a dummy text view that is refit until it matches the real target width and height up to a certain threshold factor
         * 
         * @param targetFieldWidth The width that the TextView currently has and wants filled
         * @param targetFieldHeight The width that the TextView currently has and wants filled
         */
        private void refitText(String text, int targetFieldWidth, int targetFieldHeight) {
    
            // Variables need to be visible outside the loops for later use. Remember size is in pixels
            float lowerTextSize = MIN_TEXT_SIZE;
            float upperTextSize = MAX_TEXT_SIZE;
    
            // Force the text to wrap. In principle this is not necessary since the dummy TextView
            // already does this for us but in rare cases adding this line can prevent flickering
            this.setMaxWidth(targetFieldWidth);
    
            // Padding should not be an issue since we never define it programmatically in this app
            // but just to to be sure we cut it off here
            targetFieldWidth = targetFieldWidth - this.getPaddingLeft() - this.getPaddingRight();
            targetFieldHeight = targetFieldHeight - this.getPaddingTop() - this.getPaddingBottom();
    
            // Initialize the dummy with some params (that are largely ignored anyway, but this is
            // mandatory to not get a NullPointerException)
            mTestView.setLayoutParams(new LayoutParams(targetFieldWidth, targetFieldHeight));
    
            // maxWidth is crucial! Otherwise the text would never line wrap but blow up the width
            mTestView.setMaxWidth(targetFieldWidth);
    
            if (mSingleLine) {
                // the user requested a single line. This is very easy to do since we primarily need to
                // respect the width, don't have to break, don't have to measure...
    
                /*************************** Converging algorithm 1 ***********************************/
                for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
    
                    // Go to the mean value...
                    testSize = (upperTextSize + lowerTextSize) / 2;
    
                    mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                    mTestView.setText(text);
                    mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    
                    if (mTestView.getMeasuredWidth() >= targetFieldWidth) {
                        upperTextSize = testSize; // Font is too big, decrease upperSize
                    } else {
                        lowerTextSize = testSize; // Font is too small, increase lowerSize
                    }
                }
                /**************************************************************************************/
    
                // In rare cases with very little letters and width > height we have vertical overlap!
                mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    
                if (mTestView.getMeasuredHeight() > targetFieldHeight) {
                    upperTextSize = lowerTextSize;
                    lowerTextSize = MIN_TEXT_SIZE;
    
                    /*************************** Converging algorithm 1.5 *****************************/
                    for (float testSize; (upperTextSize - lowerTextSize) > mThreshold;) {
    
                        // Go to the mean value...
                        testSize = (upperTextSize + lowerTextSize) / 2;
    
                        mTestView.setTextSize(TypedValue.COMPLEX_UNIT_SP, testSize / mScaledDensityFactor);
                        mTestView.setText(text);
                        mTestView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    
                        if (mTestView.getMeasuredHeight() >= targetFieldHeight) {
                            upperTextSize = testSize; // Font is too big, decrease upperSize
                        } else {
                            lowerTextSize = testSize; // Font is too small, increase lowerSize
                        }
                    }
                    /**********************************************************************************/
                }
            } else {
    
                /*********************** Converging algorithm 2 ***************************************/
                // Upper and lower size converge over time. As soon as they're close enough the loop
                // stops
                // TODO probe the algorithm for cost (ATM possibly O(n^2)) and optimize if possible
                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();
                    // int tempWidth = mTestView.getMeasuredWidth();
    
                    // LOG.debug("Measured: " + tempWidth + "x" + tempHeight);
                    // LOG.debug("TextSize: " + testSize / mScaledDensityFactor);
    
                    // ... 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
                    }
                }
                /**************************************************************************************/
    
                // It is possible that a single word is wider than the box. The Android system would
                // wrap this for us. But if you want to decide fo yourself where exactly to break or to
                // add a hyphen or something than you're going to want to implement something like this:
                mTestPaint.setTextSize(lowerTextSize);
                List<String> words = new ArrayList<String>();
    
                for (String s : text.split(" ")) {
                    Log.i("tag", "Word: " + s);
                    words.add(s);
                }
                for (String word : words) {
                    if (mTestPaint.measureText(word) >= targetFieldWidth) {
                        List<String> pieces = new ArrayList<String>();
                        // pieces = breakWord(word, mTestPaint.measureText(word), targetFieldWidth);
    
                        // Add code to handle the pieces here...
                    }
                }
            }
    
            /**
             * We are now at most the value of threshold away from the actual size. To rather undershoot than overshoot use the lower value. To match
             * different screens convert to SP first. See {@link http://developer.android.com/guide/topics/resources/more-resources.html#Dimension} for
             * more details
             */
            this.setTextSize(TypedValue.COMPLEX_UNIT_SP, lowerTextSize / mScaledDensityFactor);
            return;
        }
    
        /**
         * This method receives a call upon a change in text content of the TextView. Unfortunately it is also called - among others - upon text size
         * change which means that we MUST NEVER CALL {@link #refitText(String)} from this method! Doing so would result in an endless loop that would
         * ultimately result in a stack overflow and termination of the application
         * 
         * So for the time being this method does absolutely nothing. If you want to notify the view of a changed text call {@link #setText(CharSequence)}
         */
        @Override
        protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
            // Super implementation is also intentionally empty so for now we do absolutely nothing here
            super.onTextChanged(text, start, lengthBefore, lengthAfter);
        }
    
        @Override
        protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
            if (width != oldWidth && height != oldHeight) {
                refitText(this.getText().toString(), width, height);
            }
        }
    
        /**
         * This method is guaranteed to be called by {@link TextView#setText(CharSequence)} immediately. Therefore we can safely add our modifications
         * here and then have the parent class resume its work. So if text has changed you should always call {@link TextView#setText(CharSequence)} or
         * {@link TextView#setText(CharSequence, BufferType)} if you know whether the {@link BufferType} is normal, editable or spannable. Note: the
         * method will default to {@link BufferType#NORMAL} if you don't pass an argument.
         */
        @Override
        public void setText(CharSequence text, BufferType type) {
    
            int targetFieldWidth = this.getWidth();
            int targetFieldHeight = this.getHeight();
    
            if (targetFieldWidth <= 0 || targetFieldHeight <= 0 || text.equals("")) {
                // Log.v("tag", "Some values are empty, AutoFitText was not able to construct properly");
            } else {
                refitText(text.toString(), targetFieldWidth, targetFieldHeight);
            }
            super.setText(text, type);
        }
    
        /**
         * TODO add sensibility for {@link #setMaxLines(int)} invocations
         */
        @Override
        public void setMaxLines(int maxLines) {
            // TODO Implement support for this. This could be relatively easy. The idea would probably
            // be to manipulate the targetHeight in the refitText-method and then have the algorithm do
            // its job business as usual. Nonetheless, remember the height will have to be lowered
            // dynamically as the font size shrinks so it won't be a walk in the park still
            if (maxLines == 1) {
                this.setSingleLine(true);
            } else {
                throw new UnsupportedOperationException("MaxLines != 1 are not implemented in AutoFitText yet, use TextView instead");
            }
        }
    
        @Override
        public void setSingleLine(boolean singleLine) {
            // save the requested value in an instance variable to be able to decide later
            mSingleLine = singleLine;
            super.setSingleLine(singleLine);
        }
    }
    

    已知错误:没有't work with Android 4.03 - fonts are invisible or very small (original avalancha doesn'也可以工作)下面是该错误的解决方法:https://stackoverflow.com/a/21851239/2075875

  • 0

    试试这个

    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) { }
            };
    
  • 1

    如果您正在寻找更容易的东西:

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

    快速解决@Malachiasz描述的问题

    我已经通过在auto resize类中为此添加自定义支持来修复此问题:

    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)

  • 0

    尝试将 LayoutParamsMaxWidth 以及 MaxHeight 添加到 TextView . 它将强制布局尊重父容器而不是溢出 .

    textview.setLayoutParams(new LayoutParams(LinearLayout.MATCH_PARENT,LinearLayout.WRAP_CONTENT));
    
    int GeneralApproxWidthOfContainer = 400;
    int GeneralApproxHeightOfContainer = 600;
    textview.setMaxWidth(400);
    textview.setMaxHeight(600);`
    
  • 0

    从Android O开始,可以在xml中自动调整文本大小:

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:autoSizeTextType="uniform"
        app:autoSizeMinTextSize="12sp"
        app:autoSizeMaxTextSize="100sp"
        app:autoSizeStepGranularity="2sp"
      />
    

    Android O允许您指示TextView根据TextView的特征和边界自动扩展或收缩文本大小以填充其布局 . 此设置可以更轻松地使用动态内容优化不同屏幕上的文本大小 . 支持库26.0 Beta在Android O之前运行Android版本的设备上提供对自动调整TextView功能的完全支持 . 该库提供对Android 4.0(API级别14)及更高版本的支持 . android.support.v4.widget包中包含TextViewCompat类,以向后兼容的方式访问功能 .

相关问题