首页 文章

如何根据视图最大尺寸在多行TextView上自动调整文本大小?

提问于
浏览
20

我一直在寻找一种在textview中自动调整文本的方法 . 通过我的搜索,我找到了许多解决方案,如:

但和许多其他人一样,这些并不能解决我的问题 . 当我们使用带有多行的TextView时,它们无法按预期工作 .

基本上我的目标是这个:

Phase 1

Phase 2

Phase 3

Phase 4

如您所见,文本根据宽度,高度调整大小,并注意换行,创建多行文本视图 . 也可以改变字体 .

我解决这个问题的一个想法是这样的:

int size = CONSTANT_MAX_SIZE;
TextView tv = (TextView) findViewById(R.id.textview1)
while(Math.abs(tv.getMeasuredHeight()) >= TEXTVIEW_MAX_HEIGHT) {
    size--;
    tv.setTextSize(size);
    tv.measure(MeasureSpec.UNSPECIFIED,MeasureSpec.UNSPECIFIED);
    i++;
}

CONSTANT_MAX_SIZE是一个常量,用于定义字体的最大大小(TextView中的textsize礼节)

TEXTVIEW_MAX_HEIGHT是一个常量,用于定义textview可以具有的最大大小 .

每次textview中的文本发生更改时都会调用此方法 .

textview xml是这样的:

<TextView
     android:id="@+id/textview1"
     android:layout_width="200dp"
     android:layout_height="wrap_content"
     android:singleLine="false"
     android:inputType="textMultiLine"
     android:text=""
     android:textSize="150sp" />

由于宽度在XML中受到限制,因此只需要考虑视图的高度,因为调整它后android会在需要时自动创建多行 .

虽然这是一个潜在的解决方案并不完美(远离它)并且它不支持调整大小(当您删除文本时) .

任何sugestions和/或想法?

6 回答

  • 1

    当我等待这个问题的可能解决方案时,我一直在试验并试图找出它 .

    这个问题的最接近的解决方案是基于paint的方法 .

    基本上paint有一个叫做'breaktext'的方法,它有:

    public int breakText(CharSequence text,int start,int end,boolean measureForwards,float maxWidth,float [] measuredWidth)在API级别1中添加测量文本,如果测量的宽度超过maxWidth,则提前停止 . 返回测量的字符数,如果measuredWidth不为null,则返回测量的实际宽度 .

    我将它与绘画'getTextBounds'结合起来:

    public void getTextBounds(String text,int start,int end,Rect bounds)在API级别1中添加返回边界(由调用者分配)包含所有>字符的最小矩形,隐含原点为(0, 0) .

    所以现在我可以获得适合给定宽度和那些字符高度的字符数 .

    使用一段时间,您可以继续从要测量的字符串移动删除字符并获取行数(通过使用while(index <string.length))并将其乘以getTextBounds中获得的高度 .

    此外,您必须为每两行代表行之间的空间添加一个可变高度(在getTextBounds中不计算) .

    作为示例代码,知道多行文本高度的函数是这样的:

    public int getHeightOfMultiLineText(String text,int textSize, int maxWidth) {
        paint = new TextPaint();
        paint.setTextSize(textSize);
        int index = 0;
        int linecount = 0;
        while(index < text.length()) {
            index += paint.breakText(text,index,text.length,true,maxWidth,null);
            linecount++;
        }
    
        Rect bounds = new Rect();
        paint.getTextBounds("Yy", 0, 2, bounds);
        // obtain space between lines
        double lineSpacing = Math.max(0,((lineCount - 1) * bounds.height()*0.25)); 
    
        return (int)Math.floor(lineSpacing + lineCount * bounds.height());
    

    注意:maxWidth变量以像素为单位

    然后你必须在一段时间内调用这个方法来确定该高度的最大字体大小 . 示例代码是:

    textSize = 100;
    int maxHeight = 50;
    while(getHeightOfMultiLineText(text,textSize,maxWidth) > maxHeight)
      textSize--;
    

    不幸的是,这是我能够从上面的图像中获得方面的唯一方法(据我所知) .

    希望这对任何试图克服这一障碍的人都有帮助 .

  • 1

    你对这个问题有一个很好的解决方案,但我从不同的角度来看这个问题 . 我认为逻辑更简单,更容易理解,至少对于新的android开发人员来说 . 这个想法是通过在大字符串的空格之间插入'\ n'字符来实现的 . 然后,我将textView的编辑后的字符串设置为显示文本 . 所以,这是代码..

    private String insertNewLineCharAtString(String str, int step){
        StringBuilder sb = new StringBuilder();
        int spaceCounter = 0;
        String[] strArray = str.split(" ");
        for(int i = 0; i < strArray.length; i++){
    
    
    
            if(spaceCounter == step){
                sb.append('\n');
                sb.append(strArray[i]);
                spaceCounter = 0;
            }else{
                sb.append(" "+strArray[i]);
            }
            spaceCounter++;
    
    
        }
        return sb.toString();
    }
    

    参数很简单 . Str是我们要编辑的字符串,变量步骤定义方法将插入'/ n'字符的空格数 . 然后,你只需要这样调用:

    myTextView.setText(insertNewLineCharactersAtString(yourDisplayingStr);
    

    希望这有助于你们许多人不想深入了解TextPaint,Rects和数学函数 .

  • 17

    谢谢你的解决方案,你是超人!我已在TextView的派生类中实现了您的代码 . 代码转换为Xamarin android的C#代码 . 如果需要,可以轻松转换为Java(删除Java的第一个构造函数) .

    public class AutoFitTextView : TextView
    {
        public AutoFitTextView(System.IntPtr javaReference, Android.Runtime.JniHandleOwnership transfer)
            : base(javaReference, transfer)
        {
        }
    
        public AutoFitTextView(Context context)
            : base(context)
        {
    
        }
    
        public AutoFitTextView(Context context, IAttributeSet attrs)
            : base(context, attrs)
        {
    
        }
    
    
        public void ResizeFontSize()
        {
            int textSize = 50;
            int maxHeight = this.Height;
            while (GetHeightOfMultiLineText(this.Text, textSize, this.Width) > maxHeight)
            {
                textSize--;
            }
            float scaleFactor = Context.Resources.DisplayMetrics.ScaledDensity;
            float additionalFactor = 1.2f;
    
            TextSize = ((float)(textSize / (additionalFactor * scaleFactor)));
        }
    
    
        private int GetHeightOfMultiLineText(string text, int textSize, int maxWidth)
        {
            TextPaint paint = new TextPaint();
            paint.TextSize = textSize;
            int index = 0;
            int lineCount = 0;
            while (index < text.Length)
            {
                index += paint.BreakText(text, index, text.Length, true, maxWidth, null);
                lineCount++;
            }
    
            Rect bounds = new Rect();
            paint.GetTextBounds("Yy", 0, 2, bounds);
            // obtain space between lines
            double lineSpacing = Math.Max(0, ((lineCount - 1) * bounds.Height() * 0.25));
    
            return (int)Math.Floor(lineSpacing + lineCount * bounds.Height());
        }
    
    }
    

    这是AXML代码

    <touchtest.AutoFitTextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/tvLabel2"
            android:singleLine="false"
            android:inputType="textMultiLine"
            android:text="I am here To test this text" />
    
  • 1

    enter image description here

    enter image description here

    enter image description here

    有点改进了phamducgiam的答案 . 它显示的文字已经适合大小,而不是显示后的skretchs . 因为启动textSize 100而在编辑器中看起来很丑,但是在运行时应该正常工作 . 这里的码:

    import android.content.Context;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    import android.widget.TextView;
    
    public class FontFitTextView extends TextView {
    
        public FontFitTextView(Context context) {
            super(context);
        }
    
        public FontFitTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public FontFitTextView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            // start decreasing font size from 100
            setTextSize(TypedValue.COMPLEX_UNIT_PX, 100);
    
            // calculate dimensions. don't forget about padding
            final float maxWidth = getWidth()-(getPaddingLeft()+getPaddingRight());
            final float maxHeight = getHeight()-(getPaddingTop()+getPaddingBottom());
    
            if (maxWidth < 1.0f || maxHeight < 1.0f) {
                return;
            }
    
            CharSequence text = getText();
            int lineCount = getLineCount(maxWidth, text);
            float height = getHeight(lineCount);
            // keep decreasing font size until it fits
            while (height > maxHeight) {
                final float textSize = getTextSize();
                setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
                height = getHeight(getLineCount(maxWidth, getText()));
            }
            // show fitted textView
            requestLayout();
        }
    
        private float getHeight(int lineCount) {
            return lineCount * getLineHeight() + (lineCount > 0 ? (lineCount - 1) * getPaint().getFontSpacing() : 0);
        }
    
        private int getLineCount(float maxWidth, CharSequence text) {
            int lineCount = 0;
            int index = 0;
            final TextPaint paint = getPaint();
            while (index < text.length()) {
                index += paint.breakText(text, index, text.length(), true, maxWidth, null);
                lineCount++;
            }
            return lineCount;
        }
    }
    
  • -2

    使用the idea from nunofmendes,我编写了一个TextView派生类,它自动调整文本大小并支持多行 .

    import android.content.Context;
    import android.os.Handler;
    import android.text.Layout;
    import android.text.TextPaint;
    import android.text.TextUtils.TruncateAt;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    
    public class AutoResizeTextView extends TextView {
    
        private Handler measureHandler = new Handler();
        private Runnable requestLayout = new Runnable() {
            @Override
            public void run() {
                requestLayout();
            }
        };
    
        public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }
    
        public AutoResizeTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public AutoResizeTextView(Context context) {
            super(context);
        }
    
        @Override
        protected void onMeasure(final int widthMeasureSpec,
                                 final int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            final float maxWidth = getWidth();
            final float maxHeight = getHeight();
            if (maxWidth < 1.0f || maxHeight < 1.0f) {
                return;
            }
    
            int index = 0;
            int lineCount = 0;
            CharSequence text = getText();
            final TextPaint paint = getPaint();
            while (index < text.length()) {
                index += paint.breakText(text, index, text.length(), true, maxWidth, null);
                lineCount++;
            }
            final float height = lineCount * getLineHeight() + (lineCount > 0 ?
                    (lineCount - 1) * paint.getFontSpacing() : 0);
            if (height > maxHeight) {
                final float textSize = getTextSize();
                setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));
                measureHandler.post(requestLayout);
            }
        }
    }
    
  • 1

    可编译的固定代码:复制粘贴这个,而不是接受的,因为你需要修复它:)

    public static int getHeightOfMultiLineText(String text, int textSize, int maxWidth) {
        TextPaint paint = new TextPaint();
        paint.setTextSize(textSize);
        int index = 0;
        int lineCount = 0;
        while (index < text.length()) {
            index += paint.breakText(text, index, text.length(), true, maxWidth, null);
            lineCount++;
        }
    
        Rect bounds = new Rect();
        paint.getTextBounds("Yy", 0, 2, bounds);
        // obtain space between lines
        double lineSpacing = Math.max(0, ((lineCount - 1) * bounds.height() * 0.25));
    
        return (int) Math.floor(lineSpacing + lineCount * bounds.height());
    }
    

    需要用,谢谢 .

    Important: You need to consider the scale factor

    int textSize = 50;
        int maxHeight = boundsTv.height();
        while (getHeightOfMultiLineText(text, textSize, params.width) > maxHeight) {
            textSize--;
        }
        float scaleFactor =  mainActivity.getResources().getDisplayMetrics().scaledDensity;     
        float temp = 1.2f;
    
        tvText.setTextSize((float) (textSize / (temp*scaleFactor)));
    

相关问题