计算 Android 中 canvas 上绘制文本的基线 y
Calculate baseline y for drawing text on canvas in 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()
给定的范围内绘制文本
我用这个函数画多行文字:
void drawMultilineText(Canvas canvas, String text, float x, float y) {
for (String line: text.split("\n")) {
canvas.drawText(line, x, y, mPaintText);
y += mPaintText.descent() - mPaintText.ascent();
}
}
也请检查
Meaning of top, ascent, baseline, descent, bottom, and leading in Android's FontMetrics
单行文字可以放成一个BoringLayout to get the location of the baseline. (StaticLayout也可以,不过看起来你是在处理单行,所以我们就用BoringLayout .)
下面的代码展示了如何将文本放入布局中以提取基线。一旦基线已知(相对于顶部的零),就可以在 canvas.
上我们喜欢的地方绘制文本
MyView.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="Ă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>
我正在使用以下代码绘制文本。
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()
我用这个函数画多行文字:
void drawMultilineText(Canvas canvas, String text, float x, float y) {
for (String line: text.split("\n")) {
canvas.drawText(line, x, y, mPaintText);
y += mPaintText.descent() - mPaintText.ascent();
}
}
也请检查 Meaning of top, ascent, baseline, descent, bottom, and leading in Android's FontMetrics
单行文字可以放成一个BoringLayout to get the location of the baseline. (StaticLayout也可以,不过看起来你是在处理单行,所以我们就用BoringLayout .)
下面的代码展示了如何将文本放入布局中以提取基线。一旦基线已知(相对于顶部的零),就可以在 canvas.
上我们喜欢的地方绘制文本MyView.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="Ă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>