Android TextView 中真正的顶部对齐文本

Posted

技术标签:

【中文标题】Android TextView 中真正的顶部对齐文本【英文标题】:Truly top-aligning text in Android TextView 【发布时间】:2014-12-28 02:13:31 【问题描述】:

我正在尝试在 android 中显示 TextView 以使视图中的文本顶部对齐:

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);

    // Create container layout
    FrameLayout layout = new FrameLayout(this);

    // Create text label
    TextView label = new TextView(this);
    label.setTextSize(TypedValue.COMPLEX_UNIT_PX, 25);     // 25 pixels tall
    label.setGravity(Gravity.TOP + Gravity.CENTER);        // Align text top-center
    label.setPadding(0, 0, 0, 0);                          // No padding
    Rect bounds = new Rect();
    label.getPaint().getTextBounds("gdyl!", 0, 5, bounds); // Measure height
    label.setText("good day, world! "+bounds.top+" to "+bounds.bottom);
    label.setTextColor      (0xFF000000);                  // Black text
    label.setBackgroundColor(0xFF00FFFF);                  // Blue background

    // Position text label
    FrameLayout.LayoutParams layoutParams = 
            new FrameLayout.LayoutParams(300, 25, Gravity.LEFT + Gravity.TOP);
                                                            // also 25 pixels tall
    layoutParams.setMargins(50, 50, 0, 0);
    label.setLayoutParams(layoutParams);

    // Compose screen
    layout.addView(label);
    setContentView(layout);

此代码输出以下图像:

注意事项:

蓝色框高 25 像素,与要求的一样 文本边界也根据要求报告为 25 像素高 (6 - (-19) = 25) 文本不是从标签顶部开始,而是在其上方有一些填充,忽略 setPadding() 这会导致文本在底部被剪裁,即使从技术上讲该框足够高

我如何告诉 TextView 在框的最顶部开始文本?

我对可能的答案有两个限制:

我确实需要保持文本顶部对齐,所以如果有一些底部对齐或垂直居中的技巧,我不能使用它,因为我有 TextView 比它高的场景需要。 我有点兼容性问题,所以如果可能的话,我想坚持使用早期 Android API 中可用的调用(最好是 1,但绝对不超过 7)。

【问题讨论】:

我建议你看看这个; ***.com/questions/7549182/…我相信你的 textSize 是 25 px,但你的 textView 的高度超过 25,(我尝试 32 px 并且它完美地居中所有布局。) 【参考方案1】:

TextViews 在画布上使用抽象类android.text.Layout 到draw the text:

canvas.drawText(buf, start, end, x, lbaseline, paint);

垂直偏移lbaseline 计算为行的底部减去字体的下降:

int lbottom = getLineTop(i+1);int lbaseline = lbottom - getLineDescent(i);

getLineTopgetLineDescent 两个被调用函数是抽象的,但是可以在 BoringLayout 中找到一个简单的实现(见图... :),它只返回其值 mBottommDesc .这些在其init 方法中计算如下:

if (includepad) 
    spacing = metrics.bottom - metrics.top;
 else 
    spacing = metrics.descent - metrics.ascent;


if (spacingmult != 1 || spacingadd != 0) 
    spacing = (int)(spacing * spacingmult + spacingadd + 0.5f);


mBottom = spacing;

if (includepad) 
    mDesc = spacing + metrics.top;
 else 
    mDesc = spacing + metrics.ascent;

这里,includepad 是一个布尔值,用于指定文本是否应包含额外的填充以允许超出指定上升的字形。可以通过 TextView 的 setIncludeFontPadding 方法设置(正如@ggc 指出的那样)。

如果将 includepad 设置为 true(默认值),则文本将使用字体度量的 top-field 给出的基线定位。否则文本的基线取自descent-field。

所以,从技术上讲,这应该意味着我们需要做的就是关闭 IncludeFontPadding,但不幸的是,这会产生以下结果:

原因是字体报告 -23.2 作为其上升,而边界框报告顶部值为 -19。我不知道这是字体中的“错误”还是应该是这样的。不幸的是,FontMetrics 没有提供任何与边界框报告的 19 匹配的值,即使您尝试以某种方式将报告的 240dpi 屏幕分辨率与 72dpi 字体点的定义结合起来,所以没有“官方”方法来解决这个问题。

但是,当然,可用信息可用于破解解决方案。有两种方法可以做到:

单独使用 IncludeFontPadding,即设置为 true

double top = label.getPaint().getFontMetrics().top;
label.setPadding(0, (int) (top - bounds.top - 0.5), 0, 0);

即设置垂直填充以补偿从文本边界报告的 y 值和字体度量的顶部值的差异。结果:

将 IncludeFontPadding 设置为 false

double ascent = label.getPaint().getFontMetrics().ascent;
label.setPadding(0, (int) (ascent - bounds.top - 0.5), 0, 0);
label.setIncludeFontPadding(false);

即设置垂直填充以补偿从文本边界报告的 y 值和字体度量的上升值的差异。结果:

请注意,将 IncludeFontPadding 设置为 false 并没有什么神奇之处。 两个版本都应该工作。它们产生不同结果的原因是当字体度量的浮点值转换为整数时,舍入误差略有不同。碰巧在这种特殊情况下,将 IncludeFontPadding 设置为 false 看起来会更好,但对于不同的字体或字体大小,这可能会有所不同。调整顶部填充的计算以产生与 BoringLayout 使用的计算相同的精确舍入误差可能相当容易。我还没有这样做,因为我宁愿使用“无错误”字体,但如果我有时间我可能会稍后添加它。那么,IncludeFontPadding 设置为 false 还是 true 应该是无关紧要的。

【讨论】:

@androiddeveloper 我猜你需要重写 setTextSize 方法并让它在新大小的一些代表性高字母(TIHdi 等)上快速执行 getTextBounds 以获得最大上限,然后只需使用它来相应地设置填充。并确保至少调用一次 setTextSize 方法(可能在新的构造函数中)。 @androiddeveloper 顺便说一句:如果你这样做,会有字母(可能是汉字等)不再适合你的 TextView 并且会在顶部(可能是底部)被切断!因此,请确保使用您计划在应用程序中使用的所有字母来测试您的结果,如果您为用户提供编辑字段,这可能比您最初认为的要多得多。例如,我敢肯定,有些人会在他们最喜欢的角色名称中使用元音变音或类似名称。 @androiddeveloper 这里有两个问题:一方面,font-metric 的上升和下降应该描述标准字符上下移动的程度基线。出于某种原因,这似乎与我为普通字符获得的边界框不匹配。对我来说,这似乎是字体(或 Android 文本库)中的一个“错误”,因为我本来希望 |ascent| + |下降|等于我指定的字体大小,但事实并非如此。另一方面,字体指定了顶部和底部,这为某些字符留出了更多空间。 @androiddeveloper 我个人会尝试找到没有这个问题的字体,如果我绝对必须使用有这个问题的字体,我会确保检查所有字符我希望用来确保它们不会被夹住。但是,老实说,最简单的解决方案可能是将 TextView 的位置高 20 像素,使它们高 40 像素,并在顶部填充中添加 20。这样,您仍然可以可靠地定位文本,并且不会有任何剪切问题。您唯一不能做的就是为标签添加背景颜色... @androiddeveloper 它可能会增加将捕获触摸事件的标签区域...顺便说一句:另一个问题:如果您在文本上使用阴影层进行阴影或模糊发光,阴影还需要适合 TextView 的尺寸以避免剪裁!【参考方案2】:

如果您的 TextView 在其他布局中,请确保检查它们之间是否有足够的空间。您可以在父视图的底部添加一个填充,看看您是否获得了全文。它对我有用!

示例:您在 FrameLayout 中有一个 textView,但 FrameLayout 太小并且正在剪切您的 textView。向您的 FrameLayout 添加填充以查看它是否有效。

编辑:改变这一行

     FrameLayout.LayoutParams layoutParams = 
            new FrameLayout.LayoutParams(300, 25, Gravity.LEFT + Gravity.TOP);

对于这一行

    FrameLayout.LayoutParams layoutParams = 
            new FrameLayout.LayoutParams(300, 50, Gravity.LEFT + Gravity.TOP);

这将使框变大,并且以同样的方式留出足够的空间来显示您的文本。

添加这一行

     label.setIncludeFontPadding(false);

这将删除周围的字体填充并让文本可见。但在你的情况下唯一不起作用的是它不会完全显示像'g'这样的字母在行下......也许你将不得不稍微改变盒子或文本的大小(比如2-3) 如果你真的想让它工作。

【讨论】:

?不确定我是否按照您的回答...标签没有被任何其他元素遮挡,因为您可以看到我设置的 25 像素的全高...只是它以某种方式将文本放置在其中它不适合标签内。 (见示例代码) 但是您至少尝试过我的答案吗?我告诉过你它对我有用,只是尝试一下不会有任何伤害。 当然我可以让标签变大,但这只能解决这个特定的情况,我需要让它变大多少取决于确切的字体、大小等。因此,虽然您的回答肯定会修复我发布的示例,但我希望有一种方法可以通过摆脱上面的填充,将文本实际调整到它应该需要的大小。例如,您的解决方案会使蓝色框变大,这不是我在设计中想要的。 好吧,我终于得到你想要的了。你必须添加这个: label.setIncludeFontPadding(false);这将删除周围的字体填充并让文本可见。但在你的情况下唯一不起作用的是它不会完全显示像'g'这样的字母,在线下......好吧,看看你是否可以解决它! 这似乎回答了这个问题,所以请考虑将其标记为已回答。

以上是关于Android TextView 中真正的顶部对齐文本的主要内容,如果未能解决你的问题,请参考以下文章

将 ImageSpan 对齐到 TextView 的顶部

如何在android的textview中对齐文本并正确对齐

Android将textview向右对齐

Android Studio TextView 文本选择处理程序图标位置

Android TextView 文本对齐

如何使文本在 Android TextView 向右对齐