簡體   English   中英

如何使ImageSpan按基線對齊

[英]How to make ImageSpan align by baseline

問題 :為什么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);
    }
}

我的布局中有兩個視圖: TextViewView以顯示基線

<?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略低於基線。

在此輸入圖像描述

那是對的,錯誤的。 由於ALIGN_BASELINE的定義如下,因此drawable的垂直位置是關閉的:

一個常量,指示此跨度的底部應與周圍文本的基線對齊。

這顯然沒有發生。 出了什么問題,請看這篇文章的結尾。 我建議以下內容進行修復:

// 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:行的底部。

對於解決方案代碼和使用的模擬器, transYDynamicDrawableSpan計算為94,即,框將從頂部繪制94個像素。 top是178,而基線定義為零,所以178 - 94 = 84像素,這是盒子的高度或b.getBounds().bottom 這檢查出來。

在同一模擬器中的DynamicDrawableSpan#draw()中,bottom = 224,而drawable的高度仍然是84,如上所示。 為什么底部224? 這是字體的行高。

transY現在計算為224 - 84 = 140像素。 這會將框的底部放在線的底部但在基線下方。

再做一次調整:

transY -= paint.getFontMetricsInt().descent;

在測試中, paint.getFontMetricsInt().descent是41,所以transY現在變為99.由於94是正確的答案,99將盒子放置為5個像素太低,這就是你所看到的。

有關Android字體指標的說明,請參閱Android 101:排版

沒有看到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;
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM