绘制文本
文字纵向居中的算法(Y轴居中),横向居中用 Paint.Align.CENTER 即可
获取文字的上下左右位置,计算中心点,然后偏移即可:
//文字位置 aint.textAlign = Paint.Align.CENTER //获取文字的位置信息,相对于 baseLine 的左上右下位置 paint.getTextBounds("哈哈哈", 0, "哈哈哈".length, rect) //计算文字中点 val offset = (rect.top + rect.bottom) / 2 canvas.drawText("哈哈哈", (width / 2).toFloat(), (height / 2).toFloat() - offset, paint)
优势是:让文字非常居中,不管在任何地方。
缺点:如果文字会变动,就会导致重新计算中心点,然后文字的位置可能会发生改变,这样看起来很不舒服。
使用上线和下线,
val fontMetrics = Paint.FontMetrics() paint.getFontMetrics(fontMetrics) val offset = (fontMetrics.ascent + fontMetrics.descent) / 2 canvas.drawText("aaaa", (width / 2).toFloat(), (height / 2).toFloat() - offset, paint)
使用这种方式,即时文字会变,它对应的位置也不会变
让文字贴顶部
paint.getFontMetrics(fontMetrics) val offset = (fontMetrics.ascent + fontMetrics.descent) / 2 paint.textAlign = Paint.Align.LEFT canvas.drawText("aaaa", 0f, 0f - offset, paint)
如果文字是居左的,并且文字稍微有点大,就会发现文字无法紧贴左边,解决:
使用 getTextBounds 获取的 left 值作为偏移。这个 left 不会计算左边空白的地方。
paint.getTextBounds("aaaa", 0, "aaaa".length, rect) paint.textSize = 150f paint.textAlign = Paint.Align.LEFT canvas.drawText("aaaa", -(rect.left.toFloat()), 0f, paint)
让文字绘制的时候从 负的地方开始绘制即可。
绘制多行文字
class LinsTextView : View { val text = "Hilt 是 Android 的依赖注入库,是基于 Dagger 。可以说 Hilt 是专门为 Andorid 打造的。" + "\n" + "\u200B Hilt 创建了一组标准的 组件和作用域。这些组件会自动集成到 Android 程序中的生命周期中。在使用的时候可以指定使用的范围,事情作用在对应的生命周期当中。" + "————————————————\n" + "版权声明:本文为CSDN博主「345丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。" + "原文链接:https://blog.csdn.net/baidu_40389775/article/details/107095700" val textPaint = TextPaint() lateinit var staticLayout: StaticLayout constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) init { textPaint.textSize = dp2px(15f) } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) /** * 4,左对齐 * 5,文字空隙,默认为 1 * 6,添加空隙 * 7,纵向是否要加额外的高度 */ staticLayout = StaticLayout( text, textPaint, width, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false ) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) staticLayout.draw(canvas) } }
绘制多行文字,根据计算换行
class LinsTextView : View { val text = "Hilt 是 Android 的依赖注入库,是基于 Dagger 。可以说 Hilt 是专门为 Andorid 打造的。" + "Hilt 创建了一组标准的 组件和作用域。这些组件会自动集成到 Android 程序中的生命周期中。在使用的时候可以指定使用的范围,事情作用在对应的生命周期当中。" + "版权声明:本文为CSDN博主「345丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。" + "原文链接:https://blog.csdn.net/baidu_40389775/article/details/107095700" val paint = Paint(Paint.ANTI_ALIAS_FLAG) var bitmap: Bitmap val cutWith = floatArrayOf() constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) init { paint.textSize = dp2px(15f) bitmap = getAvatar(dp2px(100f)) } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.drawBitmap(bitmap, width - dp2px(100f), 100f, paint) /** * 2,是否正向绘制 * 3,View 的宽度 * 4,拿到截取的宽度 * return 第一行的位置 */ var index = paint.breakText(text, true, width.toFloat(), cutWith) //绘制第一行 canvas.drawText(text, 0, index, 0f, 50f, paint) //绘制第二行 var oldIndex = index index = paint.breakText( text, index, text.length, true, (width - bitmap.width).toFloat(), cutWith ) canvas.drawText(text, oldIndex, oldIndex + index, 0f, (50 + paint.fontSpacing), paint) //绘制第三行 oldIndex = index index = paint.breakText( text, oldIndex, text.length, true, (width - bitmap.width).toFloat(), cutWith ) canvas.drawText(text, oldIndex, oldIndex + index, 0f, (50 + (paint.fontSpacing * 2)), paint) } fun getAvatar(width: Float): Bitmap { val options = BitmapFactory.Options() //设置 true,就只会取到宽高 options.inJustDecodeBounds = true //拿到宽高 BitmapFactory.decodeResource(resources, R.drawable.avatar, options) //使用宽高,重新获取图片,对性能有一定好处 options.inJustDecodeBounds = false options.inDensity = options.outWidth options.inTargetDensity = width.toInt() return BitmapFactory.decodeResource(resources, R.drawable.avatar, options) } }
效果如上,如果旁边有别的东西就需要手动的换行,这种效果是使用 StaticLayout 做不到的。但是可以通过 breakText 做到。
就是获取当前行能够显示多少文字,然后在显示多少文字,接着获取下一个可以显示多少。。以此类推。
上面只是一种比较 low 的写法,能看清楚原理后,就可以通过 for 循环完成。获取到 文字的高度和底部位置,然后和图片的位置进行判断,以此确定要显示文字位置即可。