问题:为什么ImageSpan
ALIGN_BASELINE
不能准确地与基线对齐,以及如何解决对齐问题?
在我的活动中,我创建一个SpannableString
并将部分字符串替换为ImageSpan
。 ImageSpan
使用24x24像素的纯黑色png图像,并设置为ALIGN_BASELINE
。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView myTextView = findViewById(R.id.myTextView);
SpannableString ss = new SpannableString("Hello world!");
Drawable d = ContextCompat.getDrawable(this, R.drawable.box);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan imageSpan = new ImageSpan(d, ImageSpan.ALIGN_BASELINE);
ss.setSpan(imageSpan, 2, 5, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
myTextView.setText(ss);
}
}
布局中有两个视图:TextView
和View
用于显示基线
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/myTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff9800"
android:textSize="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBaseline_toBaselineOf="@id/myTextView"
android:background="#de4caf50"/>
</android.support.constraint.ConstraintLayout>
如您所见,该行已正确对齐TextView
的基线,但ImageSpan
略低于基线。
答案 0 :(得分:3)
那是,好,错。由于ALIGN_BASELINE
的定义如下,因此可绘制对象的垂直放置处于关闭状态:
一个常量,指示该跨度的底部应与周围文本的基线对齐。
这显然没有发生。请参阅这篇文章的末尾,了解出了什么问题。我建议您进行以下修复:
// Override draw() to place the drawable correctly when ALIGN_BASELINE.
ImageSpan imageSpan = new ImageSpan(d, ImageSpan.ALIGN_BASELINE) {
public void draw(Canvas canvas, CharSequence text, int start,
int end, float x, int top, int y, int bottom,
@NonNull Paint paint) {
if (mVerticalAlignment != ALIGN_BASELINE) {
super.draw(canvas, text, start, end, x, top, y, bottom, paint);
return;
}
Drawable b = getDrawable();
canvas.save();
// If we set transY = 0, then the drawable will be drawn at the top of the text.
// y is the the distance from the baseline to the top of the text, so
// transY = y will draw the top of the drawable on the baseline. We want the // bottom of the drawable on the baseline, so we subtract the height
// of the drawable.
int transY = y - b.getBounds().bottom;
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
};
此代码产生以下图像,我认为是正确的。
为什么没有在基线上绘制框?
以下是从DynamicDrawableSpan#draw()绘制框的来源:
public void draw(@NonNull Canvas canvas, CharSequence text,
@IntRange(from = 0) int start, @IntRange(from = 0) int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
int transY = bottom - b.getBounds().bottom;
if (mVerticalAlignment == ALIGN_BASELINE) {
transY -= paint.getFontMetricsInt().descent;
}
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
还有DynamicDrawableSpan#draw()的文档。 draw()
引起我们兴趣的唯一论点是bottom
:
bottom int:行的底部。
对于解决方案代码和使用的仿真器,transY
由DynamicDrawableSpan
计算为94,即该框将从顶部开始绘制94个像素。 top
为178,而基线定义为零,因此178-94 = 84像素,这是框或b.getBounds().bottom
的高度。签出。
在同一模拟器中的DynamicDrawableSpan#draw()
中,bottom = 224,而可绘制对象的高度仍为84,如上所示。为什么底部224?这是字体的行高。
transY
现在计算为224-84 = 140像素。这会将框的底部置于行的底部,但位于基线之下。
又进行了一次调整:
transY -= paint.getFontMetricsInt().descent;
在测试中,paint.getFontMetricsInt().descent
是41,所以transY
现在变成了99。由于94是正确的答案,因此99将框放置得过低了5个像素,这就是您所看到的。
有关Android字体指标的描述,请参见Android 101: Typography。
答案 1 :(得分:0)
没有看到drawable的xml,我会说这行是错误的
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
原因如下:
Drawable.getIntrinsicHeight()返回 -1 ,对于getIntrinsicWidth()也是如此。
这意味着您要使用以下参数调用setBounds():
d.setBounds(0, 0, -1, -1);
/**
* Specify a bounding rectangle for the Drawable. This is where the drawable
* will draw when its draw() method is called.
*/
public void setBounds(int left, int top, int right, int bottom) {
... // implementation
}
编辑:
package android.graphics.drawable;
public abstract class Drawable {
...
/**
* Returns the drawable's intrinsic height.
* <p>
* Intrinsic height is the height at which the drawable would like to be
* laid out, including any inherent padding. If the drawable has no
* intrinsic height, such as a solid color, this method returns -1.
*
* @return the intrinsic height, or -1 if no intrinsic height
*/
public int getIntrinsicHeight() {
return -1;
}
}