Paint API之—— MaskFilter(面具)

在 Android 绘图中,Paint 是控制绘制样式的核心类,而 MaskFilter 是 Paint 的一个高级功能,用于为绘制内容添加“面具”效果,修改绘制内容的边缘或整体外观。MaskFilter 常用于实现模糊、阴影或浮雕等视觉效果,增强 UI 的表现力。本文将详细解析 Paint 的 MaskFilter API,包括定义、类型、用法、代码示例、结合 Canvas 和 Path 的实战应用,以及注意事项,结合中文讲解,适合希望深入理解绘图效果的开发者。


一、MaskFilter 基础

1. 定义

  • MaskFilter:Paint 的一个属性,用于修改绘制内容的 Alpha 通道(透明度通道),生成特殊效果,如模糊、浮雕或阴影。
  • android.graphics.MaskFilter
  • 作用
  • 改变绘制内容的边缘或整体外观。
  • 常用于实现模糊(Blur)、浮雕(Emboss)等效果。
  • 通过操作 Alpha 通道,影响绘制区域的透明度或纹理。
  • 常见场景
  • 文字模糊或发光。
  • 图形边缘阴影。
  • 类似 Photoshop 的滤镜效果。

2. MaskFilter 子类

Android 提供了以下几种 MaskFilter 子类:

  • BlurMaskFilter:模糊效果,支持不同模糊样式(如外边缘模糊、整体模糊)。
  • EmbossMaskFilter(已废弃):浮雕效果,模拟光源照射的立体感(API 28+ 不推荐)。
  • 自定义 MaskFilter:开发者可继承 MaskFilter 实现自定义效果(较少使用)。

3. 工作原理

  • MaskFilter 修改 Paint 的 Alpha 通道,生成一个“蒙版”,影响绘制内容的外观。
  • Canvas 使用 Paint 绘制时,MaskFilter 会对像素进行处理,生成模糊或特殊效果。
  • 仅对具有 Alpha 通道的绘制内容(如 ARGB_8888 格式的 Bitmap)生效。

二、BlurMaskFilter 详解

BlurMaskFilter 是 MaskFilter 的主要子类,用于实现模糊效果,是实际开发中最常用的 MaskFilter。

1. 构造函数

BlurMaskFilter(float radius, BlurMaskFilter.Blur style)
  • radius:模糊半径(单位:像素),值越大模糊越强,0 表示无模糊。
  • style:模糊样式,枚举值:
  • NORMAL:整体模糊(内容和边缘)。
  • SOLID:内容实心,边缘模糊。
  • OUTER:内容透明,边缘模糊(发光效果)。
  • INNER:内容模糊,边缘实心。

2. 主要属性

  • 模糊半径:控制模糊程度,建议 5-25,过大可能导致性能问题。
  • 样式选择
  • NORMAL:适合文字或图形整体模糊。
  • SOLID:适合发光边框。
  • OUTER:适合外发光效果。
  • INNER:适合内部柔化。

3. 使用方式

  • 设置 Paint 的 MaskFilter
  paint.maskFilter = BlurMaskFilter(10f, BlurMaskFilter.Blur.NORMAL)
  • 清除 MaskFilter
  paint.maskFilter = null

三、实战示例:模糊文字与图形

以下是一个完整的示例,展示如何使用 BlurMaskFilter 结合 CanvasPath 实现模糊文字和图形的绘制,支持动态切换模糊样式。

1. 自定义 View(BlurEffectView)

创建一个自定义 View,绘制模糊文字和圆形,支持点击切换模糊样式。

import android.content.Context
import android.graphics.BlurMaskFilter
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class BlurEffectView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private val paint = Paint().apply {
        isAntiAlias = true // 抗锯齿
        color = Color.BLUE // 默认颜色
        textSize = 60f // 文字大小
        style = Paint.Style.FILL // 填充样式
    }
    private val blurStyles = arrayOf(
        BlurMaskFilter.Blur.NORMAL,
        BlurMaskFilter.Blur.SOLID,
        BlurMaskFilter.Blur.OUTER,
        BlurMaskFilter.Blur.INNER
    )
    private var currentStyleIndex = 0 // 当前模糊样式索引

    init {
        // 点击切换模糊样式
        setOnClickListener {
            currentStyleIndex = (currentStyleIndex + 1) % blurStyles.size
            paint.maskFilter = BlurMaskFilter(20f, blurStyles[currentStyleIndex])
            invalidate() // 触发重绘
        }
        // 初始化默认模糊
        paint.maskFilter = BlurMaskFilter(20f, blurStyles[currentStyleIndex])
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // 移动画布到中心
        canvas.translate(width / 2f, height / 4f)

        // 绘制模糊文字
        val text = "Blur Text"
        canvas.drawText(text, -paint.measureText(text) / 2, 0f, paint)

        // 绘制模糊圆形
        canvas.translate(0f, height / 4f)
        paint.color = Color.RED
        canvas.drawCircle(0f, 0f, 100f, paint)

        // 绘制当前样式提示
        paint.maskFilter = null // 移除模糊
        paint.color = Color.BLACK
        paint.textSize = 40f
        val styleName = when (blurStyles[currentStyleIndex]) {
            BlurMaskFilter.Blur.NORMAL -> "NORMAL"
            BlurMaskFilter.Blur.SOLID -> "SOLID"
            BlurMaskFilter.Blur.OUTER -> "OUTER"
            BlurMaskFilter.Blur.INNER -> "INNER"
        }
        canvas.drawText("Style: $styleName", -paint.measureText("Style: $styleName") / 2, height / 4f, paint)
    }
}
  • 说明
  • Paint:设置文字和圆形的颜色、样式,应用 BlurMaskFilter
  • Canvas:绘制文字和圆形,使用 translate 调整位置。
  • 交互:点击切换模糊样式(NORMAL、SOLID、OUTER、INNER)。
  • 效果
    • 初始显示模糊文字和圆形(NORMAL 样式)。
    • 点击切换不同模糊效果,中心显示当前样式名称。

2. 布局文件(activity_main.xml)

将自定义 View 放入布局。

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <com.example.BlurEffectView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

3. 主 Activity(MainActivity)

初始化 Activity,无需额外逻辑。

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

4. 运行效果

  • 初始状态:显示蓝色模糊文字(“Blur Text”)、红色模糊圆形(NORMAL 样式),下方显示“Style: NORMAL”。
  • 点击交互:每次点击切换模糊样式(NORMAL → SOLID → OUTER → INNER),文字和圆形更新模糊效果。
  • 视觉效果
  • NORMAL:整体模糊。
  • SOLID:内容实心,边缘模糊。
  • OUTER:内容透明,边缘发光。
  • INNER:内容模糊,边缘实心。

四、结合 Canvas 和 Path 的高级应用

1. 使用 Path 绘制复杂形状

结合 Path 和 BlurMaskFilter 绘制模糊的自定义形状(如心形)。

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.translate(width / 2f, height / 4f)

    // 绘制模糊心形
    val path = Path().apply {
        moveTo(0f, -50f)
        cubicTo(0f, -100f, -100f, -50f, -50f, 0f)
        cubicTo(-100f, 50f, 0f, 100f, 0f, 100f)
        cubicTo(0f, 100f, 100f, 50f, 50f, 0f)
        cubicTo(100f, -50f, 0f, -100f, 0f, -50f)
    }
    paint.color = Color.RED
    paint.maskFilter = BlurMaskFilter(20f, blurStyles[currentStyleIndex])
    canvas.drawPath(path, paint)

    // 绘制提示文字
    paint.maskFilter = null
    paint.color = Color.BLACK
    paint.textSize = 40f
    val styleName = when (blurStyles[currentStyleIndex]) {
        BlurMaskFilter.Blur.NORMAL -> "NORMAL"
        BlurMaskFilter.Blur.SOLID -> "SOLID"
        BlurMaskFilter.Blur.OUTER -> "OUTER"
        BlurMaskFilter.Blur.INNER -> "INNER"
    }
    canvas.drawText("Style: $styleName", -paint.measureText("Style: $styleName") / 2, height / 2f, paint)
}
  • 效果:绘制红色模糊心形,点击切换模糊样式。

2. 结合 Canvas 变换

使用 Canvas 的旋转和平移增强效果。

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.translate(width / 2f, height / 4f)

    // 保存画布状态
    canvas.save()
    canvas.rotate(45f) // 旋转 45 度
    paint.color = Color.BLUE
    paint.maskFilter = BlurMaskFilter(20f, blurStyles[currentStyleIndex])
    canvas.drawRect(-100f, -100f, 100f, 100f, paint)
    canvas.restore()

    // 绘制提示文字
    paint.maskFilter = null
    paint.color = Color.BLACK
    paint.textSize = 40f
    val styleName = when (blurStyles[currentStyleIndex]) {
        BlurMaskFilter.Blur.NORMAL -> "NORMAL"
        BlurMaskFilter.Blur.SOLID -> "SOLID"
        BlurMaskFilter.Blur.OUTER -> "OUTER"
        BlurMaskFilter.Blur.INNER -> "INNER"
    }
    canvas.drawText("Style: $styleName", -paint.measureText("Style: $styleName") / 2, height / 2f, paint)
}
  • 效果:绘制旋转的模糊矩形,点击切换模糊样式。

五、结合 WebView 和 Socket 的场景

1. 结合 WebView

  • 场景:将模糊效果的图形渲染为 Bitmap,传递给 WebView 显示。
  • 实现
  val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
  val canvas = Canvas(bitmap)
  draw(canvas) // 调用 onDraw
  val stream = ByteArrayOutputStream()
  bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
  val base64 = android.util.Base64.encodeToString(stream.toByteArray(), android.util.Base64.DEFAULT)
  webView.loadUrl("javascript:displayImage('data:image/png;base64,$base64')")
  • JavaScript
  function displayImage(base64) {
      document.getElementById('blurImage').src = base64;
  }
  • 布局
  <WebView
      android:id="@+id/webView"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
  • 注意:确保 Bitmap 使用 ARGB_8888 格式支持 Alpha 通道。

2. 结合 Socket

  • 场景:通过 TCP Socket 传输模糊样式索引,客户端更新 MaskFilter。
  • 实现(客户端):
  fun updateBlurStyleFromSocket() {
      GlobalScope.launch(Dispatchers.IO) {
          try {
              Socket("192.168.1.100", 12345).use { socket ->
                  val input = BufferedReader(InputStreamReader(socket.getInputStream()))
                  val styleIndex = input.readLine().toInt()
                  runOnUiThread {
                      currentStyleIndex = styleIndex % blurStyles.size
                      paint.maskFilter = BlurMaskFilter(20f, blurStyles[currentStyleIndex])
                      invalidate()
                  }
              }
          } catch (e: Exception) {
              Log.e("Socket", "错误: ${e.message}")
          }
      }
  }
  • 服务器(简单示例):
  import java.io.PrintWriter
  import java.net.ServerSocket

  fun startServer() {
      val serverSocket = ServerSocket(12345)
      while (true) {
          val clientSocket = serverSocket.accept()
          Thread {
              val out = PrintWriter(clientSocket.getOutputStream(), true)
              out.println("2") // 发送 OUTER 样式 (索引 2)
              clientSocket.close()
          }.start()
      }
  }
  • 权限
  <uses-permission android:name="android.permission.INTERNET" />

六、常见问题及注意事项

  1. 性能问题
  • 原因:BlurMaskFilter 计算复杂,模糊半径越大,性能开销越高。
  • 解决
    • 限制模糊半径(如 5-25)。
    • 使用硬件加速(默认开启):
      xml <application android:hardwareAccelerated="true">
    • 预渲染复杂图形到 Bitmap:
      kotlin val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888) val canvas = Canvas(bitmap) canvas.drawCircle(100f, 100f, 50f, paint)
  1. 兼容性
  • EmbossMaskFilter:API 28+ 已废弃,推荐使用 BlurMaskFilter 或自定义 Shader。
  • Android 4.4+:硬件加速支持更流畅的模糊效果。
  • WebView:确保 Bitmap 格式为 ARGB_8888
  1. 内存管理
  • Bitmap 使用:结合 WebView 或 Socket 时,优化 Bitmap 加载:
    kotlin val options = BitmapFactory.Options().apply { inSampleSize = 2 inPreferredConfig = Bitmap.Config.ARGB_8888 }
  • 回收
    kotlin if (!bitmap.isRecycled) bitmap.recycle()
  1. 错误处理
  • Socket 错误
    kotlin try { socket.connect() } catch (e: IOException) { Log.e("Socket", "连接失败: ${e.message}") }
  • 空 Paint:确保 Paint 初始化正确。
  1. 效果优化
  • 动态调整半径:通过动画改变模糊半径:
    kotlin ValueAnimator.ofFloat(10f, 30f).apply { duration = 1000 addUpdateListener { paint.maskFilter = BlurMaskFilter(it.animatedValue as Float, BlurMaskFilter.Blur.NORMAL) invalidate() } start() }
  • 结合 Path:确保 Path 闭合以支持填充模糊。

七、学习建议与实践

  1. 学习路径
  • 掌握 BlurMaskFilter 的四种样式(NORMAL、SOLID、OUTER、INNER)。
  • 结合 Canvas 和 Path 实现复杂形状模糊。
  • 学习动态效果(如动画、Socket 更新)。
  • 探索 WebView 和 Socket 集成。
  1. 实践项目
  • 简单项目:绘制模糊文字或圆形,切换样式。
  • 进阶项目:实现模糊心形或动态模糊动画。
  • 高级项目:通过 Socket 实时更新模糊效果,或在 WebView 中显示。
  1. 调试工具
  • Layout Inspector:检查 View 绘制效果。
  • Logcat:记录 Paint 和 MaskFilter 参数。
  • Wireshark:调试 Socket 数据。
  • Chrome DevTools:调试 WebView。
  1. 推荐资源
  • Paint 文档:https://developer.android.com/reference/android/graphics/Paint
  • MaskFilter 文档:https://developer.android.com/reference/android/graphics/MaskFilter
  • 自定义 View 教程:https://developer.android.com/training/custom-views/custom-drawing

八、总结

  • MaskFilter:Paint 的高级 API,修改 Alpha 通道,生成模糊或特殊效果。
  • BlurMaskFilter
  • 支持 NORMAL、SOLID、OUTER、INNER 四种样式。
  • 通过模糊半径控制效果强度。
  • 实战示例
  • 绘制模糊文字和圆形,点击切换样式。
  • 结合 Path 绘制模糊心形。
  • 集成 WebView 和 Socket 实现动态更新。
  • 注意事项
  • 性能优化:限制模糊半径,使用硬件加速。
  • 内存管理:回收 Bitmap。
  • 错误处理:Socket 和网络异常。
  • 推荐
  • 从简单模糊文字开始,逐步实现复杂形状和动态效果。
  • 生产环境使用 Glide 或 Lottie 替代复杂模糊。

如果需要更详细的代码(如动态模糊动画、复杂 Path 形状)或特定场景的讲解,请告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注