首页 > 解决方案 > 计算基线 y 以在 Android 中的画布上绘制文本

问题描述

我正在使用以下代码来绘制文本。

    final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    final Rect rect = new Rect();
    paint.getTextBounds(text, 0, text.length(), rect);

    final int width = rect.width();
    final int height = rect.height();

    Bitmap rendered = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

    final Canvas gOfRendered = new Canvas(rendered);

    paint.setColor(Color.WHITE);

    gOfRendered.drawText(
            text, 0, height - paint.getFontMetrics().descent - paint.getFontMetrics().leading, paint
    );

    gOfRendered.drawColor(0x11ffffff); // to see the bounds

在此处输入图像描述

Canvas.drawText需要 y 坐标作为基线。上面输出中的某些字符串(例如“back”)没有下降。paint.getFontMetrics().descent它被削减了,在这些情况下减去是没有意义的。如何确保正确计算基线?

或者,是否有一种文本绘制方法采用原点 y 坐标而不是基线?

我正在寻找一种在给出的范围内精确绘制文本的方法Paint.getTextBounds()

标签: javaandroidandroid-canvas

解决方案


可以将单行文本放入BoringLayout以获取基线的位置。(也可以使用StaticLayout,但看起来您正在处理单行,所以我们将使用BoringLayout。)

下面的代码显示了如何将文本放入布局中以提取基线。一旦知道基线(相对于顶部的零),就可以在画布上我们喜欢的位置绘制文本。

我的视图.kt

class MyView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    private val textToDisplay: String
    private var textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
        textSize = 98f
        color = Color.WHITE
    }

    // Layout for single lines
    private var boringLayout: BoringLayout

    // Contains just the text.
    private val wordBounds = Rect()

    private val wordBoundsFillPaint = Paint().apply {
        color = 0x55ffffff
        style = Paint.Style.FILL
    }

    init {
        context.obtainStyledAttributes(attrs, R.styleable.MyView, 0, 0).apply {
            textToDisplay = getString(R.styleable.MyView_android_text) ?: "Nothing"
        }.recycle()

        val metrics = BoringLayout.isBoring(textToDisplay, textPaint)
            ?: throw IllegalArgumentException("\"$textToDisplay\" is not boring.")

        // Get the text bounds that adhere tightly to the text.
        textPaint.getTextBounds(textToDisplay, 0, textToDisplay.length, wordBounds)

        // Get the layout for the text. These bounds include additional spacing used in the layout.
        boringLayout =
            BoringLayout.make(
                textToDisplay,
                textPaint,
                textPaint.measureText(textToDisplay).toInt(), Layout.Alignment.ALIGN_NORMAL,
                0f, 0f,
                metrics,
                false
            )

        val textBaseline = boringLayout.getLineBaseline(0)
        wordBounds.top = wordBounds.top + textBaseline
        wordBounds.bottom = wordBounds.bottom + textBaseline
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.withTranslation(
            // Center the words within the view.
            (width - boringLayout.width).toFloat() / 2,
            (height - boringLayout.height).toFloat() / 2
        ) {
            drawRect(wordBounds, wordBoundsFillPaint)
            // Using BoringLayout to draw text is preferred, but drawText() will work here as well.
            boringLayout.draw(this)
//            drawText(textToDisplay, 0f, boringLayout.getLineBaseline(0).toFloat(), textPaint)
        }
    }
}

在此处输入图像描述

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.example.textbaseline.MyView
        android:id="@+id/myView"
        android:layout_width="200dp"
        android:layout_height="75dp"
        android:background="@android:color/black"
        android:text="Retrograde"
        app:layout_constraintBottom_toTopOf="@+id/myView2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <com.example.textbaseline.MyView
        android:id="@+id/myView2"
        android:layout_width="200dp"
        android:layout_height="75dp"
        android:layout_marginTop="8dp"
        android:background="@android:color/black"
        android:text="&#258;pple"
        app:layout_constraintBottom_toTopOf="@+id/myView3"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/myView" />

    <com.example.textbaseline.MyView
        android:id="@+id/myView3"
        android:layout_width="200dp"
        android:layout_height="75dp"
        android:layout_marginTop="8dp"
        android:background="@android:color/black"
        android:text="back"
        app:layout_constraintBottom_toTopOf="@+id/myView4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/myView2" />

    <com.example.textbaseline.MyView
        android:id="@+id/myView4"
        android:layout_width="200dp"
        android:layout_height="75dp"
        android:layout_marginTop="8dp"
        android:background="@android:color/black"
        android:text="scene"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/myView3" />

</androidx.constraintlayout.widget.ConstraintLayout>

推荐阅读