当前位置: 首页 > 知识库问答 >
问题:

用于Android的自动适应TextView

相野
2023-03-14

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

可悲的是,即使有很多线程和帖子(以及建议的解决方案)在谈论这个问题(这里,这里和这里的例子),但没有一个实际工作得很好。

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

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

>

  • 应允许使用任何字体、字体、样式和字符集。

    应同时处理宽度和高度

    没有截断,除非文本不能适合,因为限制,我们给它(例如:太长的文本,太小的可用大小)。然而,我们可以要求水平/垂直滚动条,如果我们愿意,只是为了这些情况。

    应允许多行或单行。在多行的情况下,允许最大和最小行。

    计算速度不应该很慢。使用循环来寻找最佳大小?至少优化它,不要每次都以1递增你的采样。

    在多行的情况下,应该允许更喜欢调整大小或使用更多的行,和/或允许使用“\n”字符自己选择行。

    我试过很多例子(包括我写过的链接),我也试过修改它们来处理我已经讲过的案例,但是没有一个是真正有效的。

    我制作了一个示例项目,它允许我直观地查看TextView是否正确地自动适应。

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

    代码如下(也可在此获得):

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

    有没有人知道一个解决这个常见问题的方法,它能起到实际的作用?

    即使是一个解决方案,它的功能比我所写的要少得多,例如,一个只有恒定数量的文本,并根据其大小调整其字体的解决方案,但从来没有奇怪的小故障,也没有使文本与可用空间相比变得太大/太小。

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

  • 共有1个答案

    百里锋
    2023-03-14

    感谢Martinh在这里的简单修复,此代码还处理了Android:DrawableLeftAndroid:DrawableLightAndroid:DrawableTopAndroid:DrawableBottom标记。

    我在这里的回答应该会让你高兴自动缩放TextView文本以适应范围

    我修改了您的测试用例:

    @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开发者的要求,我在这里发布代码:

    最终效果:

    布局文件示例:

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

    和Java代码:

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

    警告:

    但要注意Android ;3.1(蜂巢)中已解决的bug。

     类似资料:
    • 问题内容: 首先让我解释一下我的目标。我试图使改变的属性。一个构造函数有两个领域:和。我想设置动画,使其在屏幕上显示为不断缩小的圆圈。 您可以通过想象PacMan来描绘此动画。想象一下他的嘴巴是闭合的。这种动画效果类似于他越来越张开上颚,直到不再有吃豆人为止。 现在…我在实现此问题上有两个问题。首先,创建后,就没有内置的更改方法。这使我想到了第一个问题:是否有任何方法可以覆盖和实现某些方法?还是我

    • 我正在Android Studio做一个项目。突然,自动导入在android studio中对android类停止工作,比如android.Widgets.TextView等。在我的设置中,自动导入是启用的。如果我编写import android.widget.TextView,那么TextView将显示在auto import框中。

    • 问题内容: 如果我为桌面启动它,它运行得很好,但是为我的Android导出后,它在我启动该应用程序后立即崩溃。 所以我的问题…: 它适用于台式机而不适用于我的Android有什么问题? 。 Logcat 我是Java的新手,所以我希望它是正确的部分:x 问题答案: 有一些人例外,logcat说: 这是由于您的模拟器未将gpu用于图形处理,最好的解决方案是使用手机来测试您的应用程序 由于某些原因(可

    • 当我在Android手机上调试应用程序时,我绝对没有遇到任何错误,但在AVD上,我一开始就得到了一个NullPointerException()。我的Android设备在22 API的Lollipop(5.1.1)上运行,我的AVD在28 API的Pie(9.0)上运行。我在gradle中将最小SDK设置为21,编译SDK设置为28。 这是错误: E/AndroidRuntime:致命异常:主进程

    • 我用一个由 它的工作罚款与 但是如果我试着在后台用 我明白了 正在启动开发服务器。。。 事件。js:174投掷者;//未处理的“错误”事件^ 错误:EBADF:坏的文件描述符,读取发射'错误'事件在:在lazyFs.read(内部/fs/streams.js:165: 12)在FSReqWrap.wrapper[作为一个完整的](fs.js:467: 17)npm ERR!代码ELIFECYCLE

    • 问题内容: 只需通过命令行( mac terminal )将一个Android项目置于git( beanstalk )版本控制下。下一步是设置排除对象。 __ 对于已经走这条路的人: 对于Android项目,典型的.gitignore文件应该是什么样? 在Eclipse中建立的专案 问题答案: 您可以混合使用Android.gitignore: 使用Eclipse.gitignore: