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" />