代码之家  ›  专栏  ›  技术社区  ›  Weizhi

如何使ImageSpan按基线对齐

  •  1
  • Weizhi  · 技术社区  · 7 年前

    字体为什么是 ImageSpan ALIGN_BASELINE 未准确对齐基线以及如何解决此对齐问题?

    在我的活动中,我创建了一个 SpannableString 并将部分字符串替换为 图像跨度 . 图像跨度 对齐基线 .

    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);
        }
    }
    

    我的布局中有两个视图:a 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> 
    

    如您所见,该线已正确对齐 文本框 图像跨度 略低于基线。

    enter image description here

    2 回复  |  直到 7 年前
        1
  •  6
  •   Cheticamp    7 年前

    这是错的。可拉伸的垂直放置已禁用,因为 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();
        }
    };
    

    这段代码生成了下面的图像,我认为这是正确的。

    enter image description here


    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

    bottomint:行的底部。

    对于解决方案代码和使用的仿真器, transY 计算方法为 DynamicDrawableSpan 如94,即框将从顶部绘制94个像素。 top b.getBounds().bottom . 这是对的。

    在里面 DynamicDrawableSpan#draw() 在同一个仿真器中,bottom=224,而drawable的高度仍为84,如上所示。为什么底部是224?这是字体的行高。

    特兰西 现在计算为224-84=140像素。这将使长方体底部位于线的底部,但低于基线。

    还进行了一次调整:

    transY -= paint.getFontMetricsInt().descent;
    

    paint.getFontMetricsInt().descent 特兰西

    看见 Android 101: Typography 有关Android字体度量的说明。

        2
  •  0
  •   Tomislav Hofman    7 年前

    如果没有看到drawable的xml,我会说这句话是错误的

    d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
    

    原因如下:

    ,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;
        }
    
    }