c
使用canvas绘制文字非常简单,但文字绘制根据baseLine无法剧中,网上说法很多,有点麻烦,这里用到一个非常简单的办法,先来看下文字的绘制参考线
图片直接百度复制的,不是自己画的,有时间自己画一个;
文字上下居中其实很简单
// 单行绘制 (先计算出基线和到文字中间的距离,mPaint不是TextPaint)
float offset = Math.abs(mPaint.ascent() + mPaint.descent()) / 2;
canvas.drawText(
singleTxt,
getWidth() / 2,
getHeight() / 2 + offset,
mPaint
);
4多行绘制有两种办法:
先来看下StaticLayout
// 多行绘制
StaticLayout staticLayout = new StaticLayout(
txtStr, // 需要绘制的文本
mTextPaint, // 画笔对象(这里需要TextPaint)
getWidth() / 2, // layout的宽度,文本超出宽度时自动换行
Layout.Alignment.ALIGN_NORMAL, // layout的对其方式
1f, // 文字行间距,根据文字大小比例计算 0.8f 1.2f
0, // 文字行间距,填写具体值
false // 纵向是否加额外高度
);
// 默认从0,0开始绘制,如果需要调整位置,可通过移动Canvas的方式解决
canvas.save();
canvas.translate(getWidth()/4,0);
staticLayout.draw(canvas);
canvas.restore();
StaticLayout很好用,系统的TextView里面也用到了,但是如果有自己的需求,随意在任何位置换行就无法实现了。
比如有这样的需求,如何绘制?这里可使用breakText()进行随意折行
class ImageBeginTextView constructor(context: Context) : View(context) {
private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val txtStr = "人之初,性本善。性相近,习相远。苟不教,性乃迁。教之道,贵以专。" +
"昔孟母,择邻处。子不学,断机杼。窦燕山,有义方。教五子,名俱扬。养不教,父之过。"
// 图片左右padding值
private var mPadding = UnitUtil.dp2px(5)
// 文字大小
private var txtSize = UnitUtil.sp2px(16)
// 图片宽度(绘制文字的时候使用)
private var imgWidth = UnitUtil.dp2px(20)
// 声明一个长度为1的Float数组
private val measuredWidth = FloatArray(1)
private var fontMetrics: Paint.FontMetrics? = null
constructor(context: Context, attrs: AttributeSet?) : this(context) {
initXmlAttrs(context, attrs)
initPaint()
}
private fun initPaint() {
mPaint.textSize = txtSize
fontMetrics = mPaint.fontMetrics
mPaint.strokeWidth = 5f
mPaint.color = Color.RED
}
private fun initXmlAttrs(context: Context, attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageBeginTextView)
?: return
// xml中配置的dp,这里获取的数据是转换后的像素,所以不用再次转换,可直接使用 (后面默认值是像素)
mPadding = typedArray.getDimension(R.styleable.ImageBeginTextView_image_padding, 20f)
txtSize = typedArray.getDimension(R.styleable.ImageBeginTextView_text_size, 12f)
typedArray.recycle()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// 绘制图片
canvas?.drawBitmap(
// 根据单行文字高度等比例缩放图片
getAvatar(mPaint.fontSpacing.toInt(), R.drawable.flag),
mPadding, 0f,
mPaint
)
// 基线Y轴
var verticalOffset = abs(fontMetrics?.top ?: mPaint.ascent())
var start = 0
while (start < txtStr.length) {
// 首行需要减掉图片宽度和开始padding,图片宽度,图片右边padding,文字右边padding;
val maxWidth = if (start == 0)
width - mPadding - imgWidth - mPadding - mPadding
else
width - mPadding * 2
// 返回一个长度,本行绘制了多少个字
val breakTextCount = mPaint.breakText(
txtStr,
true, // 是否正向绘制
maxWidth, // 最大绘制宽度
measuredWidth // 绘制本行文字的宽度存在这个数组中
)
/*
绘制文字
text: 需要绘制的文本
start: 从哪个位置开始绘制
end: 绘制到哪里,最多绘制到最后一位
x,y: 如果是第一行,从图片后边开始绘制
paint: 画笔
*/
canvas?.drawText(
txtStr,
start,
if (start + breakTextCount > txtStr.length) txtStr.length else start + breakTextCount, //
if (start == 0) mPadding + imgWidth + mPadding else mPadding, verticalOffset,
mPaint
)
start += breakTextCount
verticalOffset += mPaint.fontSpacing
}
}
private fun getAvatar(height: Int, img: Int): Bitmap {
val options = BitmapFactory.Options()
// inJustDecodeBounds为true,不返回bitmap,只返回这个bitmap的尺寸
options.inJustDecodeBounds = true
// 从资源中读取(比较浪费资源,所以上面设置为true,只获取图片宽高)
BitmapFactory.decodeResource(resources, img, options)
// 根据缩放比例重新计算宽高
options.inDensity = options.outHeight
options.inTargetDensity = height
imgWidth = options.outWidth.toFloat() * height / options.outHeight
// 再设置为false,最后要返回bitmap
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(resources, img, options)
}
}
上面只是简单的设置了两个自定义属性,如果有需要,可随意增加
在styles.xml中添加
<declare-styleable name="ImageBeginTextView">
<!--图片左右padding,也是文字左右两边的padding-->
<attr name="image_padding" format="dimension" />
<!--文字大小-->
<attr name="text_size" format="dimension" />
</declare-styleable>
在布局文件中可直接设置参数:
<com.example.hencoderplus.view.ImageBeginTextView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:image_padding="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:text_size="30sp" />