Android - ImageSpan - 如何在文本末尾居中对齐图像
Android - ImageSpan - How to center align the image at the end of the text
我在 xml 布局中有以下内容:
注意六边形 #4 没有与文本居中对齐。我该怎么做:这是我到目前为止尝试过的方法:
为了实际获得带有 # 的视图,我膨胀了一个如下所示的视图:
//my_hexagon_button.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="0dp"
tools:ignore="MissingPrefix">
<Button
android:id="@+id/tv_icon"
fontPath="proxima_nova_semi_bold.otf"
android:layout_width="16dp"
android:layout_height="17.5dp"
android:layout_marginBottom="5dp"
android:layout_marginLeft="10dp"
android:alpha=".25"
android:background="@drawable/hexagon"
android:clickable="true"
android:contentDescription="@string/content_description"
android:focusable="false"
android:padding="0dp"
android:text="4"
android:textColor="@color/white"
android:textSize="8dp"
/>
</LinearLayout>
膨胀视图后,我复制了它的绘图缓存,并在 ImageSpan 中使用它。这是我获取绘图缓存副本的方法:
public Bitmap getIconBitmap() {
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout myRoot = new LinearLayout(getActivity());
// inflate and measure the button then grab its image from the view cache
ViewGroup parent = (ViewGroup) inflater.inflate(R.layout.my_hexagon_button, myRoot);
TextView tv = (TextView) parent.findViewById(R.id.tv_icon);
parent.setDrawingCacheEnabled(true);
parent.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
parent.layout(0, 0, parent.getMeasuredWidth(), parent.getMeasuredHeight());
parent.buildDrawingCache(true);
// if you need bounds on the view, swap bitmap for a drawable and call setbounds, im not using bounds
Bitmap b = Bitmap.createBitmap(parent.getDrawingCache());
parent.setDrawingCacheEnabled(false); // clear drawing cache
return b;
}
所以现在我有一个位图,它看起来像我附加的绘图中的六边形 #4 图像。现在让我们在 ImageSpan 中使用它:
public Spannable createImageSpan(TextView tv,Bitmap bitmapIcon) {
Spannable span = new SpannableString(tv.getText());
int start = span.length() - 1;
int end = span.length();
ImageSpan image = new ImageSpan(new BitmapDrawable(getResources(), bitmapIcon),ImageSpan.ALIGN_BASELINE);
span.setSpan(image, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return span;
}
然后我只是在我的文本视图上设置了那个跨度。也不要忘记在可绘制对象上设置边界,否则它不会显示,它可以工作,但图像未在文本中居中对齐。注意它是如何下降到底部的。我怎样才能彻底解决这个问题?
你可以试试我的CenteredImageSpan
。
您可以通过计算 transY -= (paint.getFontMetricsInt().descent / 2 - 8);
在 draw
方法中自定义。 (祝你好运。:))
public class CenteredImageSpan extends ImageSpan {
private WeakReference<Drawable> mDrawableRef;
// Extra variables used to redefine the Font Metrics when an ImageSpan is added
private int initialDescent = 0;
private int extraSpace = 0;
public CenteredImageSpan(Context context, final int drawableRes) {
super(context, drawableRes);
}
public CenteredImageSpan(Drawable drawableRes, int verticalAlignment) {
super(drawableRes, verticalAlignment);
}
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
// if (fm != null) {
// Paint.FontMetricsInt pfm = paint.getFontMetricsInt();
// // keep it the same as paint's fm
// fm.ascent = pfm.ascent;
// fm.descent = pfm.descent;
// fm.top = pfm.top;
// fm.bottom = pfm.bottom;
// }
if (fm != null) {
// Centers the text with the ImageSpan
if (rect.bottom - (fm.descent - fm.ascent) >= 0) {
// Stores the initial descent and computes the margin available
initialDescent = fm.descent;
extraSpace = rect.bottom - (fm.descent - fm.ascent);
}
fm.descent = extraSpace / 2 + initialDescent;
fm.bottom = fm.descent;
fm.ascent = -rect.bottom + fm.descent;
fm.top = fm.ascent;
}
return rect.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
// int drawableHeight = b.getIntrinsicHeight();
// int fontAscent = paint.getFontMetricsInt().ascent;
// int fontDescent = paint.getFontMetricsInt().descent;
// int transY = bottom - b.getBounds().bottom + // align bottom to bottom
// (drawableHeight - fontDescent + fontAscent) / 2; // align center to center
int transY = bottom - b.getBounds().bottom;
// this is the key
transY -= (paint.getFontMetricsInt().descent / 2 - 8);
// int bCenter = b.getIntrinsicHeight() / 2;
// int fontTop = paint.getFontMetricsInt().top;
// int fontBottom = paint.getFontMetricsInt().bottom;
// int transY = (bottom - b.getBounds().bottom) -
// (((fontBottom - fontTop) / 2) - bCenter);
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
// Redefined locally because it is a private member from DynamicDrawableSpan
private Drawable getCachedDrawable() {
WeakReference<Drawable> wr = mDrawableRef;
Drawable d = null;
if (wr != null)
d = wr.get();
if (d == null) {
d = getDrawable();
mDrawableRef = new WeakReference<>(d);
}
return d;
}
}
EDIT
我是这样实现上面的代码的:
Drawable myIcon = getResources().getDrawable(R.drawable.btn_feedback_yellow);
int width = (int) Functions.convertDpToPixel(75, getActivity());
int height = (int) Functions.convertDpToPixel(23, getActivity());
myIcon.setBounds(0, 0, width, height);
CenteredImageSpan btnFeedback = new CenteredImageSpan(myIcon, ImageSpan.ALIGN_BASELINE);
ssBuilder.setSpan(
btnFeedback, // Span to add
getString(R.string.text_header_answer).length() - 1, // Start of the span (inclusive)
getString(R.string.text_header_answer).length(), // End of the span (exclusive)
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);// Do not extend the span when text add later
您可以使用此 class 将 ImageSpan 与文本对齐
public class VerticalImageSpan extends ImageSpan {
public VerticalImageSpan(Drawable drawable) {
super(drawable);
}
/**
* update the text line height
*/
@Override
public int getSize(Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fontMetricsInt) {
Drawable drawable = getDrawable();
Rect rect = drawable.getBounds();
if (fontMetricsInt != null) {
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
int fontHeight = fmPaint.descent - fmPaint.ascent;
int drHeight = rect.bottom - rect.top;
int centerY = fmPaint.ascent + fontHeight / 2;
fontMetricsInt.ascent = centerY - drHeight / 2;
fontMetricsInt.top = fontMetricsInt.ascent;
fontMetricsInt.bottom = centerY + drHeight / 2;
fontMetricsInt.descent = fontMetricsInt.bottom;
}
return rect.right;
}
/**
* see detail message in android.text.TextLine
*
* @param canvas the canvas, can be null if not rendering
* @param text the text to be draw
* @param start the text start position
* @param end the text end position
* @param x the edge of the replacement closest to the leading margin
* @param top the top of the line
* @param y the baseline
* @param bottom the bottom of the line
* @param paint the work paint
*/
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
Drawable drawable = getDrawable();
canvas.save();
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
int fontHeight = fmPaint.descent - fmPaint.ascent;
int centerY = y + fmPaint.descent - fontHeight / 2;
int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2;
canvas.translate(x, transY);
drawable.draw(canvas);
canvas.restore();
}
}
多亏了这个answer
对于 API >29,您可以使用 ImageSpan.ALIGN_CENTER
常量来执行此操作。 (下面的 Kotlin 代码示例。)
val image: ImageSpan = ImageSpan(
BitmapDrawable(resources, bitmapIcon),
ImageSpan.ALIGN_CENTER);
span.setSpan(image, start, end, 0);
如果您需要支持低于 29 的 API 水平(我想大多数人会持续一段时间),您仍然需要像 RoShan Shan 的回答那样子类化 ImageSpan。您只需要严格重写 draw 即可使行为正常工作,但是:
class CenteredImageSpanSubclass(
context: Context,
bitmap: Bitmap): ImageSpan(context, bitmap) {
override fun draw(...) {
canvas.save()
val transY = (bottom - top) / 2 - drawable.bounds.height() / 2
canvas.translate(x, transY.toFloat())
drawable.draw(canvas)
canvas.restore()
}
}
这是我的解决方案,它支持单行和多行文本
class CenteredImageSpan(dr: Drawable) : ImageSpan(dr) {
private var mDrawableRef: WeakReference<Drawable>? = null
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
val d = cachedDrawable
val rect: Rect = d!!.bounds
val pfm = paint.fontMetricsInt
if (fm != null) {
fm.ascent = -rect.height() / 2 + pfm.ascent / 2
fm.descent = Math.max(0, rect.height() / 2 + pfm.ascent / 2)
fm.top = fm.ascent
fm.bottom = fm.descent
}
return rect.right
}
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, @NonNull paint: Paint) {
val b = cachedDrawable!!
canvas.save()
var transY = (bottom + top) / 2 - b.bounds.height() / 2
canvas.translate(x, transY.toFloat())
b.draw(canvas)
canvas.restore()
}
// Redefined locally because it is a private member from DynamicDrawableSpan
private val cachedDrawable: Drawable?
private get() {
val wr: WeakReference<Drawable>? = mDrawableRef
var d: Drawable? = null
if (wr != null) d = wr.get()
if (d == null) {
d = drawable
mDrawableRef = WeakReference(d)
}
return d
}
}
我发现处理所有对齐问题的更简单方法是换一种方法。我们将创建一个 imageSpan,但位图将来自膨胀视图。
所以像这样膨胀你的图像视图(对齐方式都可以在这里用边距调整):
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/myImage"
android:paddingLeft="3dp"
android:paddingTop="1dp"
android:paddingRight="3dp"
android:paddingBottom="1dp"
android:layout_marginEnd="6dp"
tools:text="for sale" />
您可以通过编程方式创建父视图组,并根据需要将此电视添加到其中。
现在让我们从这台电视上抓取位图:
private fun getBitmap(myTextView: View): Bitmap {
val bitmap = Bitmap.createBitmap(myTextView.width, myTextView.height, Bitmap.Config.ARGB_8888)
val myCanvas = Canvas(bitmap)
view.draw(myCanvas)
return bitmap
}
既然您已将图像作为位图,只需将其应用于 imageSpan 并按照您认为合适的方式添加即可。我喜欢这种方式,因为我能够控制跨度对齐方式,而不是依赖如此复杂的字体指标。
我在 xml 布局中有以下内容:
注意六边形 #4 没有与文本居中对齐。我该怎么做:这是我到目前为止尝试过的方法:
为了实际获得带有 # 的视图,我膨胀了一个如下所示的视图:
//my_hexagon_button.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="0dp"
tools:ignore="MissingPrefix">
<Button
android:id="@+id/tv_icon"
fontPath="proxima_nova_semi_bold.otf"
android:layout_width="16dp"
android:layout_height="17.5dp"
android:layout_marginBottom="5dp"
android:layout_marginLeft="10dp"
android:alpha=".25"
android:background="@drawable/hexagon"
android:clickable="true"
android:contentDescription="@string/content_description"
android:focusable="false"
android:padding="0dp"
android:text="4"
android:textColor="@color/white"
android:textSize="8dp"
/>
</LinearLayout>
膨胀视图后,我复制了它的绘图缓存,并在 ImageSpan 中使用它。这是我获取绘图缓存副本的方法:
public Bitmap getIconBitmap() {
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout myRoot = new LinearLayout(getActivity());
// inflate and measure the button then grab its image from the view cache
ViewGroup parent = (ViewGroup) inflater.inflate(R.layout.my_hexagon_button, myRoot);
TextView tv = (TextView) parent.findViewById(R.id.tv_icon);
parent.setDrawingCacheEnabled(true);
parent.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
parent.layout(0, 0, parent.getMeasuredWidth(), parent.getMeasuredHeight());
parent.buildDrawingCache(true);
// if you need bounds on the view, swap bitmap for a drawable and call setbounds, im not using bounds
Bitmap b = Bitmap.createBitmap(parent.getDrawingCache());
parent.setDrawingCacheEnabled(false); // clear drawing cache
return b;
}
所以现在我有一个位图,它看起来像我附加的绘图中的六边形 #4 图像。现在让我们在 ImageSpan 中使用它:
public Spannable createImageSpan(TextView tv,Bitmap bitmapIcon) {
Spannable span = new SpannableString(tv.getText());
int start = span.length() - 1;
int end = span.length();
ImageSpan image = new ImageSpan(new BitmapDrawable(getResources(), bitmapIcon),ImageSpan.ALIGN_BASELINE);
span.setSpan(image, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return span;
}
然后我只是在我的文本视图上设置了那个跨度。也不要忘记在可绘制对象上设置边界,否则它不会显示,它可以工作,但图像未在文本中居中对齐。注意它是如何下降到底部的。我怎样才能彻底解决这个问题?
你可以试试我的CenteredImageSpan
。
您可以通过计算 transY -= (paint.getFontMetricsInt().descent / 2 - 8);
在 draw
方法中自定义。 (祝你好运。:))
public class CenteredImageSpan extends ImageSpan {
private WeakReference<Drawable> mDrawableRef;
// Extra variables used to redefine the Font Metrics when an ImageSpan is added
private int initialDescent = 0;
private int extraSpace = 0;
public CenteredImageSpan(Context context, final int drawableRes) {
super(context, drawableRes);
}
public CenteredImageSpan(Drawable drawableRes, int verticalAlignment) {
super(drawableRes, verticalAlignment);
}
@Override
public int getSize(Paint paint, CharSequence text,
int start, int end,
Paint.FontMetricsInt fm) {
Drawable d = getCachedDrawable();
Rect rect = d.getBounds();
// if (fm != null) {
// Paint.FontMetricsInt pfm = paint.getFontMetricsInt();
// // keep it the same as paint's fm
// fm.ascent = pfm.ascent;
// fm.descent = pfm.descent;
// fm.top = pfm.top;
// fm.bottom = pfm.bottom;
// }
if (fm != null) {
// Centers the text with the ImageSpan
if (rect.bottom - (fm.descent - fm.ascent) >= 0) {
// Stores the initial descent and computes the margin available
initialDescent = fm.descent;
extraSpace = rect.bottom - (fm.descent - fm.ascent);
}
fm.descent = extraSpace / 2 + initialDescent;
fm.bottom = fm.descent;
fm.ascent = -rect.bottom + fm.descent;
fm.top = fm.ascent;
}
return rect.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text,
int start, int end, float x,
int top, int y, int bottom, @NonNull Paint paint) {
Drawable b = getCachedDrawable();
canvas.save();
// int drawableHeight = b.getIntrinsicHeight();
// int fontAscent = paint.getFontMetricsInt().ascent;
// int fontDescent = paint.getFontMetricsInt().descent;
// int transY = bottom - b.getBounds().bottom + // align bottom to bottom
// (drawableHeight - fontDescent + fontAscent) / 2; // align center to center
int transY = bottom - b.getBounds().bottom;
// this is the key
transY -= (paint.getFontMetricsInt().descent / 2 - 8);
// int bCenter = b.getIntrinsicHeight() / 2;
// int fontTop = paint.getFontMetricsInt().top;
// int fontBottom = paint.getFontMetricsInt().bottom;
// int transY = (bottom - b.getBounds().bottom) -
// (((fontBottom - fontTop) / 2) - bCenter);
canvas.translate(x, transY);
b.draw(canvas);
canvas.restore();
}
// Redefined locally because it is a private member from DynamicDrawableSpan
private Drawable getCachedDrawable() {
WeakReference<Drawable> wr = mDrawableRef;
Drawable d = null;
if (wr != null)
d = wr.get();
if (d == null) {
d = getDrawable();
mDrawableRef = new WeakReference<>(d);
}
return d;
}
}
EDIT
我是这样实现上面的代码的:
Drawable myIcon = getResources().getDrawable(R.drawable.btn_feedback_yellow);
int width = (int) Functions.convertDpToPixel(75, getActivity());
int height = (int) Functions.convertDpToPixel(23, getActivity());
myIcon.setBounds(0, 0, width, height);
CenteredImageSpan btnFeedback = new CenteredImageSpan(myIcon, ImageSpan.ALIGN_BASELINE);
ssBuilder.setSpan(
btnFeedback, // Span to add
getString(R.string.text_header_answer).length() - 1, // Start of the span (inclusive)
getString(R.string.text_header_answer).length(), // End of the span (exclusive)
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);// Do not extend the span when text add later
您可以使用此 class 将 ImageSpan 与文本对齐
public class VerticalImageSpan extends ImageSpan {
public VerticalImageSpan(Drawable drawable) {
super(drawable);
}
/**
* update the text line height
*/
@Override
public int getSize(Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fontMetricsInt) {
Drawable drawable = getDrawable();
Rect rect = drawable.getBounds();
if (fontMetricsInt != null) {
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
int fontHeight = fmPaint.descent - fmPaint.ascent;
int drHeight = rect.bottom - rect.top;
int centerY = fmPaint.ascent + fontHeight / 2;
fontMetricsInt.ascent = centerY - drHeight / 2;
fontMetricsInt.top = fontMetricsInt.ascent;
fontMetricsInt.bottom = centerY + drHeight / 2;
fontMetricsInt.descent = fontMetricsInt.bottom;
}
return rect.right;
}
/**
* see detail message in android.text.TextLine
*
* @param canvas the canvas, can be null if not rendering
* @param text the text to be draw
* @param start the text start position
* @param end the text end position
* @param x the edge of the replacement closest to the leading margin
* @param top the top of the line
* @param y the baseline
* @param bottom the bottom of the line
* @param paint the work paint
*/
@Override
public void draw(Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
Drawable drawable = getDrawable();
canvas.save();
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
int fontHeight = fmPaint.descent - fmPaint.ascent;
int centerY = y + fmPaint.descent - fontHeight / 2;
int transY = centerY - (drawable.getBounds().bottom - drawable.getBounds().top) / 2;
canvas.translate(x, transY);
drawable.draw(canvas);
canvas.restore();
}
}
多亏了这个answer
对于 API >29,您可以使用 ImageSpan.ALIGN_CENTER
常量来执行此操作。 (下面的 Kotlin 代码示例。)
val image: ImageSpan = ImageSpan(
BitmapDrawable(resources, bitmapIcon),
ImageSpan.ALIGN_CENTER);
span.setSpan(image, start, end, 0);
如果您需要支持低于 29 的 API 水平(我想大多数人会持续一段时间),您仍然需要像 RoShan Shan 的回答那样子类化 ImageSpan。您只需要严格重写 draw 即可使行为正常工作,但是:
class CenteredImageSpanSubclass(
context: Context,
bitmap: Bitmap): ImageSpan(context, bitmap) {
override fun draw(...) {
canvas.save()
val transY = (bottom - top) / 2 - drawable.bounds.height() / 2
canvas.translate(x, transY.toFloat())
drawable.draw(canvas)
canvas.restore()
}
}
这是我的解决方案,它支持单行和多行文本
class CenteredImageSpan(dr: Drawable) : ImageSpan(dr) {
private var mDrawableRef: WeakReference<Drawable>? = null
override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int {
val d = cachedDrawable
val rect: Rect = d!!.bounds
val pfm = paint.fontMetricsInt
if (fm != null) {
fm.ascent = -rect.height() / 2 + pfm.ascent / 2
fm.descent = Math.max(0, rect.height() / 2 + pfm.ascent / 2)
fm.top = fm.ascent
fm.bottom = fm.descent
}
return rect.right
}
override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, @NonNull paint: Paint) {
val b = cachedDrawable!!
canvas.save()
var transY = (bottom + top) / 2 - b.bounds.height() / 2
canvas.translate(x, transY.toFloat())
b.draw(canvas)
canvas.restore()
}
// Redefined locally because it is a private member from DynamicDrawableSpan
private val cachedDrawable: Drawable?
private get() {
val wr: WeakReference<Drawable>? = mDrawableRef
var d: Drawable? = null
if (wr != null) d = wr.get()
if (d == null) {
d = drawable
mDrawableRef = WeakReference(d)
}
return d
}
}
我发现处理所有对齐问题的更简单方法是换一种方法。我们将创建一个 imageSpan,但位图将来自膨胀视图。
所以像这样膨胀你的图像视图(对齐方式都可以在这里用边距调整):
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/myImage"
android:paddingLeft="3dp"
android:paddingTop="1dp"
android:paddingRight="3dp"
android:paddingBottom="1dp"
android:layout_marginEnd="6dp"
tools:text="for sale" />
您可以通过编程方式创建父视图组,并根据需要将此电视添加到其中。
现在让我们从这台电视上抓取位图:
private fun getBitmap(myTextView: View): Bitmap {
val bitmap = Bitmap.createBitmap(myTextView.width, myTextView.height, Bitmap.Config.ARGB_8888)
val myCanvas = Canvas(bitmap)
view.draw(myCanvas)
return bitmap
}
既然您已将图像作为位图,只需将其应用于 imageSpan 并按照您认为合适的方式添加即可。我喜欢这种方式,因为我能够控制跨度对齐方式,而不是依赖如此复杂的字体指标。