Auto Scale TextView Text to Fit within Bounds

后端 未结 30 2818
囚心锁ツ
囚心锁ツ 2020-11-21 05:49

I\'m looking for an optimal way to resize wrapping text in a TextView so that it will fit within its getHeight and getWidth bounds. I\'m not simply looking for

30条回答
  •  一生所求
    2020-11-21 05:58

    Providing this version of top answer rewritten on C# for those who codes on Xamarin.Android. Worked for me well.

     /**
     *               DO WHAT YOU WANT TO PUBLIC LICENSE
     *                    Version 2, December 2004
     * 
     * Copyright (C) 2004 Sam Hocevar 
     * 
     * 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; }
        }
    
        /// 
        /// 
        /// 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.
        /// 
        public class AutoResizeTextView : TextView
        {
            /// 
            /// Minimum text size for this text view
            /// 
            public static float MIN_TEXT_SIZE = 10;
    
            /// 
            /// Our ellipse string
            /// 
            private const string Ellipsis = "...";
    
    
            private float _mMaxTextSize;
    
            private float _mMinTextSize = MIN_TEXT_SIZE;
    
            /// 
            /// Register subscriber to receive resize notifications
            /// 
            public event EventHandler OnTextResize;
    
            /// 
            /// Flag for text and/or size changes to force a resize
            /// 
            private bool _needsResize;
    
            /// 
            /// Text size that is set from code. This acts as a starting point for resizing
            /// 
            private float _textSize;
    
            /// 
            /// Text view line spacing multiplier
            /// 
            private float _spacingMult = 1.0f;
    
            /// 
            /// Text view additional line spacing
            /// 
            private float _spacingAdd;
    
            /// 
            /// Add ellipsis to text that overflows at the smallest text size
            /// 
            public bool ShouldAddEllipsis { get; set; }
    
            /// 
            /// 
            /// Override the set text size to update our internal reference values
            /// 
            public override float TextSize
            {
                get => base.TextSize;
                set
                {
                    base.TextSize = value;
                    _textSize = TextSize;
                }
            }
    
            /// 
            /// Temporary upper bounds on the starting text size
            /// 
            public float MaxTextSize
            {
                get => _mMaxTextSize;
                // Set the upper text size limit and invalidate the view
                set
                {
                    _mMaxTextSize = value;
                    RequestLayout();
                    Invalidate();
                }
            }
    
            /// 
            /// Lower bounds for text size
            /// 
            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;
            }
    
            /// 
            /// 
            /// When text changes, set the force resize flag to true and reset the text size.
            /// 
            /// 
            /// 
            /// 
            /// 
            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();
            }
    
            /// 
            /// 
            /// If the text view size changed, set the force resize flag to true
            /// 
            /// 
            /// 
            /// 
            /// 
            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;
            }
    
            /// 
            /// 
            /// Override the set line spacing to update our internal reference values
            /// 
            /// 
            /// 
            public override void SetLineSpacing(float add, float mult)
            {
                base.SetLineSpacing(add, mult);
                _spacingMult = mult;
                _spacingAdd = add;
            }
    
            /// 
            /// Reset the text to the original size
            /// 
            public void ResetTextSize()
            {
                if (_textSize > 0)
                {
                    base.SetTextSize(ComplexUnitType.Px, _textSize);
                    _mMaxTextSize = _textSize;
                }
            }
    
            /// 
            /// 
            /// Resize text after measuring
            /// 
            /// 
            /// 
            /// 
            /// 
            /// 
            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);
            }
    
            /// 
            /// Resize the text size with default width and height
            /// 
            public void ResizeText()
            {
                var heightLimit = Height - PaddingBottom - PaddingTop;
                var widthLimit = Width - PaddingLeft - PaddingRight;
                ResizeText(widthLimit, heightLimit);
            }
    
            /// 
            /// Resize the text size with specified width and height
            /// 
            /// 
            /// 
            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;
            }
    
            /// 
            /// Set the text size of the text paint object and use a static layout to render text off screen before measuring
            /// 
            /// 
            /// 
            /// 
            /// 
            /// 
            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;
            }
        }
    }
    

提交回复
热议问题