书接上回,我们来讲讲如何实现“文字环绕图片”的效果吧。整体预计实现的效果如下:
日常杂谈
我喜欢看动漫,接下来的所有博客都会和我日常生活结合在一起写,这样感觉会让自己的博客会有温度,我感觉每个人都应该有自己的爱好,动漫也是一种爱好,就和女孩喜欢看剧一样,没有什么高低贵贱之分,不是说喜欢打篮球就高人一等。不虚伪不做作,这才是我,最近在看“我推的孩子”。
正文
如果是单纯需要实现自动换行的效果,那么只需要通过StaticLayout的方式就能简单地绘制对应的文字换行效果。使用也是非常简单,我就不多赘述了。
val staticLayout =
StaticLayout(
textPlain, textPaint, width, Layout.Alignment.ALIGN_NORMAL,
1f,
textPaint.fontSpacing / 2, true
)
staticLayout.draw(canvas)
但是如果希望实现我们上述所提及的文字环绕效果,通过上述的方式很明显是不够的,我们就需要具体情况具体分析,根据图片的位置来动态地确定文字应该绘制的位置。
首先我们来看一下大体的思路:
-
确定目的:我们需要在(width/2,height/4)的位置开始绘制一张图片,图片的宽度是width/2,然后图片的高度未知,因为我们需要保持图片的宽高比,总不能说,原来长方形的图片放进来之后就压缩成方形的了。
fun getAvatar(context: Context, @DrawableRes drawableRes: Int, withInWidth: Int): Bitmap { val option = BitmapFactory.Options() option.inJustDecodeBounds = true BitmapFactory.decodeResource(resources, drawableRes, option) option.apply { inDensity = outWidth inTargetDensity = withInWidth inJustDecodeBounds = false } return BitmapFactory.decodeResource(resources, drawableRes, option) }
-
在对应的位置绘制对应的图片,并存储图片的左上角的纵坐标和左下角的纵坐标值
val bitmap = getAvatar(context, R.drawable.avatar, width / 2) canvas.drawBitmap(bitmap, width/2f,height/4f, paint) val mipmapStartX = width / 4 val mipmapStartY = height / 4 val mipmapEndY = height / 4 +bitmap.height
-
逐行绘制文字,当文字绘制的纵坐标和图片的纵坐标有重合的时候,限制文字的绘制宽度
var startIndex = 0 val endIndex = textPlain.length var currentWidth = width var startY = -metrics.top var breakLen: Int = 0 while (startIndex < endIndex) { val textTop = startY + metrics.top val textBottom = textTop + (metrics.bottom - metrics.top) currentWidth = if (textBottom > mipmapStartY && textTop < mipmapEndY) { width / 2 } else width breakLen = textPaint.breakText( textPlain, startIndex, endIndex, true, currentWidth.toFloat(), null ) canvas.drawText(textPlain, startIndex, startIndex + breakLen, 0f, startY, textPaint) startIndex += breakLen startY += textPaint.fontSpacing }
就这样,整体的环绕效果绘制就完成了。如下是完整的代码:
/** * 绘制一个环绕图片的文字效果 */ class RoundAvatarTextView(context: Context, attrs: AttributeSet) : View(context, attrs) { private val paint: Paint = Paint().apply { isAntiAlias = true } private var metrics: Paint.FontMetrics = Paint.FontMetrics() private val textPaint: TextPaint = TextPaint().apply { textSize = 20.dp } val dstRectF = RectF(width/2f,height/4f,width.toFloat(),height/4f*3) private val textPlain = resources.getText(R.string.think) override fun onDraw(canvas: Canvas) { super.onDraw(canvas) textPaint.getFontMetrics(metrics) val bitmap = getAvatar(context, R.drawable.avatar, width / 2) canvas.drawBitmap(bitmap, width/2f,height/4f, paint) val mipmapStartX = width / 4 val mipmapStartY = height / 4 val mipmapEndY = height / 4 +bitmap.height var startIndex = 0 val endIndex = textPlain.length var currentWidth = width var startY = -metrics.top var breakLen: Int = 0 while (startIndex < endIndex) { val textTop = startY + metrics.top val textBottom = textTop + (metrics.bottom - metrics.top) currentWidth = if (textBottom > mipmapStartY && textTop < mipmapEndY) { width / 2 } else width breakLen = textPaint.breakText( textPlain, startIndex, endIndex, true, currentWidth.toFloat(), null ) canvas.drawText(textPlain, startIndex, startIndex + breakLen, 0f, startY, textPaint) startIndex += breakLen startY += textPaint.fontSpacing } } fun getAvatar(context: Context, @DrawableRes drawableRes: Int, withInWidth: Int): Bitmap { val option = BitmapFactory.Options() option.inJustDecodeBounds = true BitmapFactory.decodeResource(resources, drawableRes, option) option.apply { inDensity = outWidth inTargetDensity = withInWidth inJustDecodeBounds = false } return BitmapFactory.decodeResource(resources, drawableRes, option) } }