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 结合 Canvas 和 Path 实现模糊文字和图形的绘制,支持动态切换模糊样式。
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" />
六、常见问题及注意事项
- 性能问题:
- 原因: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)
- 兼容性:
- EmbossMaskFilter:API 28+ 已废弃,推荐使用
BlurMaskFilter
或自定义 Shader。 - Android 4.4+:硬件加速支持更流畅的模糊效果。
- WebView:确保 Bitmap 格式为
ARGB_8888
。
- 内存管理:
- Bitmap 使用:结合 WebView 或 Socket 时,优化 Bitmap 加载:
kotlin val options = BitmapFactory.Options().apply { inSampleSize = 2 inPreferredConfig = Bitmap.Config.ARGB_8888 }
- 回收:
kotlin if (!bitmap.isRecycled) bitmap.recycle()
- 错误处理:
- Socket 错误:
kotlin try { socket.connect() } catch (e: IOException) { Log.e("Socket", "连接失败: ${e.message}") }
- 空 Paint:确保 Paint 初始化正确。
- 效果优化:
- 动态调整半径:通过动画改变模糊半径:
kotlin ValueAnimator.ofFloat(10f, 30f).apply { duration = 1000 addUpdateListener { paint.maskFilter = BlurMaskFilter(it.animatedValue as Float, BlurMaskFilter.Blur.NORMAL) invalidate() } start() }
- 结合 Path:确保 Path 闭合以支持填充模糊。
七、学习建议与实践
- 学习路径:
- 掌握
BlurMaskFilter
的四种样式(NORMAL、SOLID、OUTER、INNER)。 - 结合 Canvas 和 Path 实现复杂形状模糊。
- 学习动态效果(如动画、Socket 更新)。
- 探索 WebView 和 Socket 集成。
- 实践项目:
- 简单项目:绘制模糊文字或圆形,切换样式。
- 进阶项目:实现模糊心形或动态模糊动画。
- 高级项目:通过 Socket 实时更新模糊效果,或在 WebView 中显示。
- 调试工具:
- Layout Inspector:检查 View 绘制效果。
- Logcat:记录 Paint 和 MaskFilter 参数。
- Wireshark:调试 Socket 数据。
- Chrome DevTools:调试 WebView。
- 推荐资源:
- 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 形状)或特定场景的讲解,请告诉我!