如何在Jetpack Compose中实现文字水印效果?
实现文字水印的其他可靠方式
我想在应用中实现如下图所示的文字水印效果,目前已经通过Canvas和Bitmap完成了实现,想问问有没有其他可靠的实现方法?

以下是我当前的Composable函数:
@Composable fun WaterMark( modifier: Modifier = Modifier, content: (@Composable BoxScope.() -> Unit)? = null, ) { val watermarkText: String = "some mutable text" val paint = Paint(Paint.ANTI_ALIAS_FLAG) paint.textSize = LocalContext.current.dpToPx(24).toFloat() paint.color = PSCoreColours.psCoreColours.onSurface.hashCode() paint.textAlign = Paint.Align.LEFT paint.alpha = (255 * 0.25).toInt() val baseline: Float = -paint.ascent() val image: Bitmap = Bitmap.createBitmap(paint.measureText(watermarkText).toInt(), (baseline + paint.descent()).toInt(), Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(image) canvas.drawText(watermarkText, 0f, baseline, paint) val rotationMatrix: Matrix = Matrix().apply { postRotate(-45f) } val rotatedImage: Bitmap = Bitmap.createBitmap(image, 0, 0, image.width, image.height, rotationMatrix, true) val pattern: ImageBitmap = rotatedImage.asImageBitmap() Box { content?.let { it() } Canvas( modifier = modifier ) { val totalWidth = size.width / pattern.width val totalHeight = size.height / pattern.height var x = 0f var y = 0f for (i in 0..totalHeight.toInt()) { y = (i * pattern.height).toFloat() for (j in 0..totalWidth.toInt()) { x = (j * pattern.width).toFloat() drawImage( pattern, colorFilter = null, topLeft = Offset(x, y), ) } } } } }
其他可行的实现方式
1. 使用Jetpack Compose的drawBehind修饰符直接绘制
无需提前生成Bitmap,直接在Composable的背景层循环绘制旋转后的文本,减少Bitmap创建和内存占用:
@Composable fun WatermarkDrawBehind( modifier: Modifier = Modifier, watermarkText: String = "some mutable text", content: @Composable () -> Unit ) { val density = LocalDensity.current val textSize = with(density) { 24.dp.toSp() } val textColor = PSCoreColours.psCoreColours.onSurface.copy(alpha = 0.25f) Box(modifier = modifier.drawBehind { val textPaint = Paint().apply { this.textSize = textSize.value color = textColor isAntiAlias = true textAlign = Paint.Align.CENTER } val textWidth = textPaint.measureText(watermarkText) val textHeight = textPaint.descent() - textPaint.ascent() val spacing = 100.dp.toPx() // 水印间的间距 // 计算覆盖整个区域的行列数 val rows = (size.height / spacing).toInt() + 2 val cols = (size.width / spacing).toInt() + 2 for (row in -1..rows) { for (col in -1..cols) { // 交错排列让水印分布更均匀 val x = col * spacing + (row % 2) * spacing / 2 val y = row * spacing drawContext.canvas.save() drawContext.canvas.rotate(-45f, x, y) drawText( text = watermarkText, x = x, y = y - textPaint.ascent(), // 调整基线实现文本垂直居中 paint = textPaint ) drawContext.canvas.restore() } } }) { content() } }
优势:
- 无需处理Bitmap的创建、旋转和回收,避免内存泄漏风险
- 贴合Compose原生绘制范式,文本变更时自动触发重绘
- 代码逻辑更简洁,减少中间对象生成
2. 使用自定义Brush实现平铺水印
创建自定义Brush,利用Shader实现文本图案的平铺,可直接通过Modifier.background应用:
fun createWatermarkBrush( text: String, textSize: Float, textColor: Color, rotation: Float = -45f, spacing: Float = 100f ): Brush { val paint = Paint().apply { this.textSize = textSize color = textColor isAntiAlias = true textAlign = Paint.Align.CENTER } val textWidth = paint.measureText(text) val textHeight = paint.descent() - paint.ascent() val bitmapSize = max(textWidth, textHeight) + spacing val bitmap = Bitmap.createBitmap(bitmapSize.toInt(), bitmapSize.toInt(), Bitmap.Config.ARGB_8888) val canvas = android.graphics.Canvas(bitmap) // 在Bitmap中心绘制单个水印 canvas.save() canvas.rotate(rotation, bitmapSize/2, bitmapSize/2) canvas.drawText(text, bitmapSize/2, bitmapSize/2 - paint.ascent(), paint) canvas.restore() val shader = BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT) return ShaderBrush(shader) } // Compose中使用示例 @Composable fun WatermarkBrushExample( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { val density = LocalDensity.current val textSize = with(density) { 24.dp.toPx() } val textColor = PSCoreColours.psCoreColours.onSurface.copy(alpha = 0.25f) // 用remember缓存Brush,避免重复创建 val watermarkBrush = remember(textSize, textColor) { createWatermarkBrush("some mutable text", textSize, textColor) } Box(modifier = modifier.background(brush = watermarkBrush)) { content() } }
优势:
- 可直接通过
Modifier.background应用,代码更简洁 - 利用
Shader的原生平铺机制,性能表现更稳定 - 适合将水印作为背景层的场景
3. 使用View系统自定义Drawable实现(兼容传统View)
若需兼容传统View系统,可自定义Drawable实现水印,再通过Compose的AndroidView引用:
class WatermarkDrawable(private val text: String) : Drawable() { private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { textSize = 24.dpToPx(context).toFloat() color = PSCoreColours.psCoreColours.onSurface.copy(alpha = 0.25f).toArgb() textAlign = Paint.Align.CENTER } private val spacing = 100.dpToPx(context).toFloat() override fun draw(canvas: android.graphics.Canvas) { val bounds = bounds val textWidth = paint.measureText(text) val textHeight = paint.descent() - paint.ascent() val rows = (bounds.height() / spacing).toInt() + 2 val cols = (bounds.width() / spacing).toInt() + 2 for (row in -1..rows) { for (col in -1..cols) { val x = col * spacing + (row % 2) * spacing / 2 + bounds.left val y = row * spacing + bounds.top canvas.save() canvas.rotate(-45f, x, y) canvas.drawText(text, x, y - paint.ascent(), paint) canvas.restore() } } } override fun setAlpha(alpha: Int) { paint.alpha = alpha } override fun setColorFilter(colorFilter: ColorFilter?) { paint.colorFilter = colorFilter } override fun getOpacity(): Int { return PixelFormat.TRANSLUCENT } private fun Context.dpToPx(dp: Int): Int { return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp.toFloat(), resources.displayMetrics).toInt() } } // Compose中使用示例 @Composable fun WatermarkDrawableExample( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Box(modifier = modifier) { content() AndroidView( modifier = Modifier.matchParentSize(), factory = { context -> View(context).apply { background = WatermarkDrawable("some mutable text") } } ) } }
优势:
- 可复用传统View系统的逻辑,适合混合开发场景
- 自定义
Drawable可在View和Compose中通用
内容的提问来源于stack exchange,提问作者Sultan
相关产品推荐
相关产品推荐

