我正在寻找一种最佳的方法来调整包装文本在一个TextView,使它将适合它的getHeight和getWidth界限。我不是简单地寻找一种方法来包装文本-我想确保它既包装,又足够小,完全适合在屏幕上。

我在StackOverflow上看到了一些需要自动调整大小的情况,但它们要么是非常特殊的情况下的hack解决方案,没有解决方案,或涉及重新绘制TextView递归直到它足够小(这是内存紧张,迫使用户观看文本收缩一步一步与每次递归)。

但我相信有人已经找到了一个很好的解决方案,它不涉及我正在做的事情:编写几个繁重的例程来解析和测量文本,调整文本的大小,然后重复,直到找到一个合适的小尺寸。

TextView使用什么例程来包装文本?难道这些不能用来预测文本是否足够小吗?

是否有一个最佳实践的方法来自动调整TextView的大小,以适应,包装,在它的getHeight和getWidth边界?


当前回答

以下是我为那些还在找工作的人找到的一些东西:

1)这里有一个解决方案,递归重新绘制的textview,直到它适合。这意味着字面上看着你的文本缩小到合适的位置,但至少当它完成时它是合适的。代码需要一些调整才能实现,但基本上已经完成了。

2)你可以试着把一个自定义的解决方案像这样,或dunni的类在这里,这是我所做的使用getPaint().measureText(str)来搜索正确的大小,但它得到了很多混乱,因为我需要它只包装在空白…

3)你可以继续寻找——我已经尝试了数不清的选择。泰德关于StaticLayout的建议对我来说没有回报,但也许有一些东西;我尝试使用StaticLayout.getEllipsis(line)来确定文本是否离开屏幕,但没有效果。在这里可以看到我的帖子(目前没有回复)。

其他回答

为那些在Xamarin.Android上编码的人提供了这个用c#重写的顶级答案版本。对我来说很管用。

 /**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */


using System;
using Android.Content;
using Android.Runtime;
using Android.Text;
using Android.Util;
using Android.Widget;
using Java.Lang;

namespace App.GuestGuide.Droid.Controls
{
    public class OnTextResizeEventArgs : EventArgs
    {
        public TextView TextView { get; set; }
        public float OldSize { get; set; }
        public float NewSize { get; set; }
    }

    /// <inheritdoc />
    /// <summary>
    /// Text view that auto adjusts text size to fit within the view.
    /// If the text size equals the minimum text size and still does not
    /// fit, append with an ellipsis.
    /// </summary>
    public class AutoResizeTextView : TextView
    {
        /// <summary>
        /// Minimum text size for this text view
        /// </summary>
        public static float MIN_TEXT_SIZE = 10;

        /// <summary>
        /// Our ellipse string
        /// </summary>
        private const string Ellipsis = "...";


        private float _mMaxTextSize;

        private float _mMinTextSize = MIN_TEXT_SIZE;

        /// <summary>
        /// Register subscriber to receive resize notifications
        /// </summary>
        public event EventHandler<OnTextResizeEventArgs> OnTextResize;

        /// <summary>
        /// Flag for text and/or size changes to force a resize
        /// </summary>
        private bool _needsResize;

        /// <summary>
        /// Text size that is set from code. This acts as a starting point for resizing
        /// </summary>
        private float _textSize;

        /// <summary>
        /// Text view line spacing multiplier
        /// </summary>
        private float _spacingMult = 1.0f;

        /// <summary>
        /// Text view additional line spacing
        /// </summary>
        private float _spacingAdd;

        /// <summary>
        /// Add ellipsis to text that overflows at the smallest text size
        /// </summary>
        public bool ShouldAddEllipsis { get; set; }

        /// <inheritdoc />
        /// <summary>
        /// Override the set text size to update our internal reference values
        /// </summary>
        public override float TextSize
        {
            get => base.TextSize;
            set
            {
                base.TextSize = value;
                _textSize = TextSize;
            }
        }

        /// <summary>
        /// Temporary upper bounds on the starting text size
        /// </summary>
        public float MaxTextSize
        {
            get => _mMaxTextSize;
            // Set the upper text size limit and invalidate the view
            set
            {
                _mMaxTextSize = value;
                RequestLayout();
                Invalidate();
            }
        }

        /// <summary>
        /// Lower bounds for text size
        /// </summary>
        public float MinTextSize
        {
            get => _mMinTextSize;
            //Set the lower text size limit and invalidate the view
            set
            {
                _mMinTextSize = value;
                RequestLayout();
                Invalidate();
            }
        }

        public AutoResizeTextView(Context context) : this(context, null)
        {
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs) : this(context, attrs, 0)
        {
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr) : base(context, attrs, defStyleAttr)
        {
            _textSize = TextSize;
        }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyleAttr, int defStyleRes) : base(context, attrs, defStyleAttr, defStyleRes)
        {
            _textSize = TextSize;
        }

        protected AutoResizeTextView(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer)
        {
            _textSize = TextSize;
        }

        /// <inheritdoc />
        /// <summary>
        /// When text changes, set the force resize flag to true and reset the text size.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="start"></param>
        /// <param name="lengthBefore"></param>
        /// <param name="lengthAfter"></param>
        protected override void OnTextChanged(ICharSequence text, int start, int lengthBefore, int lengthAfter)
        {
            _needsResize = true;
            // Since this view may be reused, it is good to reset the text size
            ResetTextSize();
        }

        /// <inheritdoc />
        /// <summary>
        /// If the text view size changed, set the force resize flag to true
        /// </summary>
        /// <param name="w"></param>
        /// <param name="h"></param>
        /// <param name="oldw"></param>
        /// <param name="oldh"></param>
        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            if (w != oldw || h != oldh)
            {
                _needsResize = true;
            }
        }

        public override void SetTextSize([GeneratedEnum] ComplexUnitType unit, float size)
        {
            base.SetTextSize(unit, size);
            _textSize = TextSize;
        }

        /// <inheritdoc />
        /// <summary>
        /// Override the set line spacing to update our internal reference values
        /// </summary>
        /// <param name="add"></param>
        /// <param name="mult"></param>
        public override void SetLineSpacing(float add, float mult)
        {
            base.SetLineSpacing(add, mult);
            _spacingMult = mult;
            _spacingAdd = add;
        }

        /// <summary>
        /// Reset the text to the original size
        /// </summary>
        public void ResetTextSize()
        {
            if (_textSize > 0)
            {
                base.SetTextSize(ComplexUnitType.Px, _textSize);
                _mMaxTextSize = _textSize;
            }
        }

        /// <inheritdoc />
        /// <summary>
        /// Resize text after measuring
        /// </summary>
        /// <param name="changed"></param>
        /// <param name="left"></param>
        /// <param name="top"></param>
        /// <param name="right"></param>
        /// <param name="bottom"></param>
        protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
        {
            if (changed || _needsResize)
            {
                var widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
                var heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
                ResizeText(widthLimit, heightLimit);
            }

            base.OnLayout(changed, left, top, right, bottom);
        }

        /// <summary>
        /// Resize the text size with default width and height
        /// </summary>
        public void ResizeText()
        {
            var heightLimit = Height - PaddingBottom - PaddingTop;
            var widthLimit = Width - PaddingLeft - PaddingRight;
            ResizeText(widthLimit, heightLimit);
        }

        /// <summary>
        /// Resize the text size with specified width and height
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void ResizeText(int width, int height)
        {
            ICharSequence text = null;

            if (!string.IsNullOrEmpty(Text))
            {
                text = new Java.Lang.String(Text);
            }

            // Do not resize if the view does not have dimensions or there is no text
            if (text == null || text.Length() == 0 || height <= 0 || width <= 0 || _textSize == 0)
            {
                return;
            }

            if (TransformationMethod != null)
            {
                text = TransformationMethod.GetTransformationFormatted(text, this);
            }

            // Get the text view's paint object
            var textPaint = Paint;
            // Store the current text size
            var oldTextSize = textPaint.TextSize;
            // If there is a max text size set, use the lesser of that and the default text size
            var targetTextSize = _mMaxTextSize > 0 ? System.Math.Min(_textSize, _mMaxTextSize) : _textSize;

            // Get the required text height
            var textHeight = GetTextHeight(text, textPaint, width, targetTextSize);

            // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
            while (textHeight > height && targetTextSize > _mMinTextSize)
            {
                targetTextSize = System.Math.Max(targetTextSize - 2, _mMinTextSize);
                textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
            }

            // If we had reached our minimum text size and still don't fit, append an ellipsis
            if (ShouldAddEllipsis && targetTextSize == _mMinTextSize && textHeight > height)
            {
                // Draw using a static layout
                // modified: use a copy of TextPaint for measuring
                var paint = new TextPaint(textPaint);
                // Draw using a static layout
                var layout = new StaticLayout(text, paint, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, false);

                // Check that we have a least one line of rendered text
                if (layout.LineCount > 0)
                {
                    // Since the line at the specific vertical position would be cut off,
                    // we must trim up to the previous line
                    var lastLine = layout.GetLineForVertical(height) - 1;
                    // If the text would not even fit on a single line, clear it
                    if (lastLine < 0)
                    {
                        Text = string.Empty;
                    }
                    // Otherwise, trim to the previous line and add an ellipsis
                    else
                    {
                        var start = layout.GetLineStart(lastLine);
                        var end = layout.GetLineEnd(lastLine);
                        var lineWidth = layout.GetLineWidth(lastLine);
                        var ellipseWidth = textPaint.MeasureText(Ellipsis);

                        // Trim characters off until we have enough room to draw the ellipsis
                        while (width < lineWidth + ellipseWidth)
                        {
                            lineWidth = textPaint.MeasureText(text.SubSequence(start, --end + 1));
                        }

                        Text = text.SubSequence(0, end) + Ellipsis;
                    }
                }
            }

            // Some devices try to auto adjust line spacing, so force default line spacing
            // and invalidate the layout as a side effect
            SetTextSize(ComplexUnitType.Px, targetTextSize);
            SetLineSpacing(_spacingAdd, _spacingMult);

            var notifyArgs = new OnTextResizeEventArgs
            {
                TextView = this,
                NewSize = targetTextSize,
                OldSize = oldTextSize
            };

            // Notify the listener if registered
            OnTextResize?.Invoke(this, notifyArgs);

            // Reset force resize flag
            _needsResize = false;
        }

        /// <summary>
        /// Set the text size of the text paint object and use a static layout to render text off screen before measuring
        /// </summary>
        /// <param name="source"></param>
        /// <param name="paint"></param>
        /// <param name="width"></param>
        /// <param name="textSize"></param>
        /// <returns></returns>
        private int GetTextHeight(ICharSequence source, TextPaint paint, int width, float textSize)
        {
            // modified: make a copy of the original TextPaint object for measuring
            // (apparently the object gets modified while measuring, see also the
            // docs for TextView.getPaint() (which states to access it read-only)
            // Update the text paint object
            var paintCopy = new TextPaint(paint)
            {
                TextSize = textSize
            };

            // Measure using a static layout
            var layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, _spacingMult, _spacingAdd, true);

            return layout.Height;
        }
    }
}

在2017年谷歌IO大会上,谷歌介绍了TextView的autoSize属性

https://youtu.be/fjUdJ2aVqE4

<android.support.v7.widget.AppCompatTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/my_text"
        app:autoSizeTextType="uniform"
        app:autoSizeMaxTextSize="10sp"
        app:autoSizeMinTextSize="6sp"
        app:autoSizeStepGranularity="1sp"/>

我希望这对你有所帮助

import android.content.Context;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.widget.TextView;

/* Based on 
 * from http://stackoverflow.com/questions/2617266/how-to-adjust-text-font-size-to-fit-textview
 */
public class FontFitTextView extends TextView {

private static float MAX_TEXT_SIZE = 20;

public FontFitTextView(Context context) {
    this(context, null);
}

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

    float size = this.getTextSize();
    if (size > MAX_TEXT_SIZE)
        setTextSize(MAX_TEXT_SIZE);
}

private void refitText(String text, int textWidth) {
    if (textWidth > 0) {
        float availableWidth = textWidth - this.getPaddingLeft()
                - this.getPaddingRight();

        TextPaint tp = getPaint();
        Rect rect = new Rect();
        tp.getTextBounds(text, 0, text.length(), rect);
        float size = rect.width();

        if (size > availableWidth)
            setTextScaleX(availableWidth / size);
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
    int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
    refitText(this.getText().toString(), parentWidth);
    this.setMeasuredDimension(parentWidth, parentHeight);
}

@Override
protected void onTextChanged(final CharSequence text, final int start,
        final int before, final int after) {
    refitText(text.toString(), this.getWidth());
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    if (w != oldw) {
        refitText(this.getText().toString(), w);
    }
}
}

注意:我使用MAX_TEXT_SIZE在文本大小大于20的情况下,因为我不想让大字体适用于我的视图,如果这不是你的情况下,你可以简单地删除它。

我为此写了一篇博客。

我根据Kirill Grouchnikov关于在新的android market应用中使用的自定义组件的博客文章创建了一个名为ResizableButton的组件。我把src代码放在这里。

另一方面,mosabua读了我的文章,并告诉我他将开源他的实现,这比我的更快。我希望他能尽快发布:)

我已经使用代码从追逐和M-WaJeEh 我发现了一些优点和缺点

从追逐

优势: 这是完美的1行TextView 劣势: 如果它超过1行自定义字体,一些文本将消失 如果它是使能椭圆,它没有为椭圆准备空间 如果是自定义字体(字体),则不支持

从M-WaJeEh

优势: 非常适合多线使用 劣势: 如果将height设置为wrap-content,这段代码将从最小大小开始,并尽可能减小到最小,而不是从setSize开始并减小有限的宽度 如果是自定义字体(字体),则不支持