在渲染到布局之前获取文本视图的高度

Posted

技术标签:

【中文标题】在渲染到布局之前获取文本视图的高度【英文标题】:Getting height of text view before rendering to layout 【发布时间】:2013-11-23 08:27:03 【问题描述】:

找不到任何好的解决方案计算 textview 高度其中设置了文本 在渲染之前 textview 到布局。请帮忙

【问题讨论】:

您尝试执行的操作可能存在问题。如果您将高度设置为某个值(例如 60dp),则可以,但如果您将其设置为 "wrap_content" ,则高度将取决于屏幕尺寸。所以您可能希望以编程方式(在 Java 代码中)创建文本视图 我正在创建电子书阅读器,为此我应该知道 textview 的高度有多少文本适合它 如果设置为 "wrap_content" ,它将根据其内容展开。如果您想确保用户不应该垂直滑动,那么您可以查看 viewflipper、viewpager 等。 你检查过这个[问题] (***.com/questions/15679147/…)。 @mobilGelistirici 文本中有可扩展的文本和不同的字体我无法计算一页有多少文本足够。这就是为什么我用空格分割文本并在添加每个元素后它应该检查 textview没有克服显示高度。为此我应该知道 textview 的高度并在每次添加文本时在渲染之前检查它 【参考方案1】:

2 个解决方案

最初使用解决方案 1,后来找到解决方案 2。两者都有效,这确实是您喜欢的。

重要的是确保所有尺寸都正确,因为在 sp 或 px 中混合字体大小会产生很大差异,具体取决于您在哪个屏幕上进行测试。

https://github.com/hanscappelle/SO-3654321 上提供了一个非常基本的示例项目

使用 TextView 和 MeasureSpec 的解决方案 1

原始问题的主要问题是下面方法中的 TextView 应该配置为我们的 TextView 应该呈现到布局。我认为这个解决方案对许多面临这个问题的人来说很有价值。

public static int getHeight(Context context, CharSequence text, int textSize, int deviceWidth, Typeface typeface,int padding) 
            TextView textView = new TextView(context);
            textView.setPadding(padding,0,padding,padding);
            textView.setTypeface(typeface);
            textView.setText(text, TextView.BufferType.SPANNABLE);
            textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, textSize);
            int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
            int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
            textView.measure(widthMeasureSpec, heightMeasureSpec);
            return textView.getMeasuredHeight();
        

以及如何使用它的示例:

// retrieve deviceWidth
int deviceWidth;
WindowManager wm = (WindowManager) textView.getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
    Point size = new Point();
    display.getSize(size);
    deviceWidth = size.x;
 else 
    deviceWidth = display.getWidth();

// the text to check for
String exampleTextToMeasure = "some example text that will be long enough to make this example split over multiple lines so we can't easily predict the final height";
//  some dimensions from dimes resources to take into account
int textSize = getContext().getResources().getDimensionPixelSize(R.dimen.text_size);
int padding = getContext().getResources().getDimensionPixelSize(R.dimen.text_padding);

// final calculation of textView height
int measuredTextHeight = getHeight(getContext(), exampleTextToMeasure, textSize, deviceWidth, TypeFace.DEFAULT, padding); 

使用 TextPaint 和 StaticLayout 的解决方案 2

此方法依赖于 TextPaint 和 StaticLayout,它还可以在我迄今为止测试的所有 API 级别上提供可靠的结果。注意尺寸单位;一切都应该以像素为单位!

来源:Measuring text height to be drawn on Canvas ( Android )

    public static int method1UsingTextPaintAndStaticLayout(
            final CharSequence text,
            final int textSize, // in pixels
            final int deviceWidth, // in pixels
            final int padding // in pixels
    ) 

        TextPaint myTextPaint = new TextPaint();
        myTextPaint.setAntiAlias(true);
        // this is how you would convert sp to pixels based on screen density
        //myTextPaint.setTextSize(16 * context.getResources().getDisplayMetrics().density);
        myTextPaint.setTextSize(textSize);
        Layout.Alignment alignment = Layout.Alignment.ALIGN_NORMAL;
        float spacingMultiplier = 1;
        float spacingAddition = padding; // optionally apply padding here
        boolean includePadding = padding != 0;
        StaticLayout myStaticLayout = new StaticLayout(text, myTextPaint, deviceWidth, alignment, spacingMultiplier, spacingAddition, includePadding);
        return myStaticLayout.getHeight();
    

【讨论】:

MeasureSpec.AT_MOST 很重要,如果文本包含任何将在显示的 TextView 大小中换行的行。 这里的顶部填充为 0 的任何原因? @treesAreEverywhere 无关紧要。这只是为了我的情况【参考方案2】:

从 support_ms 的回答中,有一个更简单的方法,它只接受一个 TextView 作为参数。

/**
 * Get the TextView height before the TextView will render
 * @param textView the TextView to measure
 * @return the height of the textView
 */
public static int getTextViewHeight(TextView textView) 
    WindowManager wm =
            (WindowManager) textView.getContext().getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();

    int deviceWidth;

    if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
        Point size = new Point();
        display.getSize(size);
        deviceWidth = size.x;
     else 
        deviceWidth = display.getWidth();
    

    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    textView.measure(widthMeasureSpec, heightMeasureSpec);
    return textView.getMeasuredHeight();

【讨论】:

我已经尝试过了,它几乎可以工作。但是,最后一行文本被截断了。我尝试对此进行测试,并在其中放置了很多非常长的乱码> 20个字符,并且被切断的行数增加了。我只能假设这个测量的高度没有考虑到下一行的字溢出。所以这个计算的高度小于实际渲染的高度,随着更大的单词和更长的文本而增加差异。 @Distraction 我应该使用 EditText makeMeasureSpec 或 inflate 上的 EditText 设置。 这个工作完美,至少对于单行的 TextView。 (这是我需要的)。请注意,由于设备的实际像素宽度通常是已知的(它经常在应用程序的其他部分中使用),并且变量“wm”未使用,因此仅此代码的最后四行就足够了。太好了! 像魅力一样工作。 :) 太棒了!谢谢好心的先生!【参考方案3】:

@support_ms 给出了很好的答案,但是当您可以先格式化 TextView 然后只使用一个参数调用静态方法时,我不确定创建新 TextView 并计算所有这些输入参数的意义, TextView 本身!

我也不确定为什么一个参数被标记为deviceWidth 我只是使用Textview 本身的宽度。我的是match_parent,我想任何TextViewwrap_content 都可能根本不起作用。但这就是你得到的。

public static int getHeight(TextView t) 
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(screenWidth(t.getContext()), View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    t.measure(widthMeasureSpec, heightMeasureSpec);
    return t.getMeasuredHeight();


public static int screenWidth(Context context)

    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    return display.getWidth();

【讨论】:

这个screenWidth方法是什么? 此方法在其他 SO 答案中可用。为了清楚起见,我添加了方法。【参考方案4】:

这是我的简单解决方案,它在绘制之前获取尺寸

https://***.com/a/40133275/1240672

【讨论】:

【参考方案5】:

渲染前获取TextView的一行

这是我基于上述想法的代码。它对我有用。

private int widthMeasureSpec;
private int heightMeasureSpec;
private int heightOfEachLine;
private int paddingFirstLine;
private void calculateHeightOfEachLine() 
    WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    Display display = wm.getDefaultDisplay();
    Point size = new Point();
    display.getSize(size);
    int deviceWidth = size.x;
    widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(deviceWidth, View.MeasureSpec.AT_MOST);
    heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
    //1 line = 76; 2 lines = 76 + 66; 3 lines = 76 + 66 + 66
    //=> height of first line = 76 pixel; height of second line = third line =... n line = 66 pixel
    int heightOfFirstLine = getHeightOfTextView("A");
    int heightOfSecondLine = getHeightOfTextView("A\nA") - heightOfFirstLine;
    paddingFirstLine = heightOfFirstLine - heightOfSecondLine;
    heightOfEachLine = heightOfSecondLine;


private int getHeightOfTextView(String text) 
    // Getting height of text view before rendering to layout
    TextView textView = new TextView(context);
    textView.setPadding(10, 0, 10, 0);
    //textView.setTypeface(typeface);
    textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.tv_size_14sp));
    textView.setText(text, TextView.BufferType.SPANNABLE);
    textView.measure(widthMeasureSpec, heightMeasureSpec);
    return textView.getMeasuredHeight();


private int getLineCountOfTextViewBeforeRendering(String text) 
    return (getHeightOfTextView(text) - paddingFirstLine) / heightOfEachLine;

注意:此代码也必须为屏幕上的真实文本视图设置

textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources().getDimension(R.dimen.tv_size_14sp));

【讨论】:

【参考方案6】:

Kotlin 扩展

fun TextView.calculateHeight(text: CharSequence = getText()): Int 
    val alignment = when(gravity) 
        Gravity.CENTER -> Layout.Alignment.ALIGN_CENTER
        Gravity.RIGHT -> Layout.Alignment.ALIGN_OPPOSITE
        else -> Layout.Alignment.ALIGN_NORMAL
    
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) 
        StaticLayout.Builder.obtain(text, 0, text.length, TextPaint(paint), width)
            .setLineSpacing(lineSpacingExtra, lineSpacingMultiplier)
            .setAlignment(alignment)
            .setIncludePad(true).build()
     else 
        @Suppress("DEPRECATION")
        StaticLayout(
            text, TextPaint(paint), width, alignment,
            lineSpacingMultiplier, lineSpacingExtra, true
        )
    .height

【讨论】:

对我来说,这种方法效果很好,但开始剪切大文本的最后一行

以上是关于在渲染到布局之前获取文本视图的高度的主要内容,如果未能解决你的问题,请参考以下文章

呈现之前的 WPF 文本框高度

滚动视图内的Android约束布局高度无效

在显示视图之前根据特定宽度获取 UIView 的高度

根据图像高度在集合视图自定义布局中获取单元格的高度

使用自动布局表格视图时如何增加表格视图单元格中的标签高度(取决于文本)?

动态单元格高度的自动布局约束