You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 06:51:18