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);
getLineTop
和 getLineDescent
两个被调用函数是抽象的,但是可以在 BoringLayout 中找到一个简单的实现(见图... :),它只返回其值 mBottom
和 mDesc
.这些在其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 中真正的顶部对齐文本的主要内容,如果未能解决你的问题,请参考以下文章