第4章 Paint详解:从颜色到滤镜,玩转画笔的十八般武艺
说实话,Paint这个类,我刚开始学Android自定义View的时候,觉得它就是个「画画的笔」。后来踩的坑多了,才发现它才是整个UI绘制的灵魂。你想想看,Canvas决定了画什么,而Paint决定了怎么画——颜色、粗细、边缘效果、甚至纹理,全看它怎么配置。
这一章,咱们就把Paint的常用属性掰开揉碎了讲。我会结合自己项目里的真实案例,帮你避开那些我曾经掉进去的坑。
4.1 颜色与样式:最基础的,也最容易出错
先说说颜色。Paint设置颜色有三种方式:
// 方式一:直接传int颜色值
paint.setColor(Color.RED);
paint.setColor(0xFFFF0000);
// 方式二:用Color.parse
paint.setColor(Color.parseColor("#FF0000"));
// 方式三:用ARGB分量
paint.setARGB(255, 255, 0, 0);
我个人习惯用第一种,简单直接。但要注意一点——setColor会覆盖掉之前设置的ColorFilter和Shader效果。嗯,这个坑我踩过,后面会细说。
再说样式。Paint有三种样式:
| 样式 | 效果 | 典型场景 |
|---|---|---|
| FILL | 填充内部 | 实心圆、矩形填充 |
| STROKE | 仅描边 | 空心圆、边框 |
| FILL_AND_STROKE | 填充+描边 | 带边框的实心图形 |
setStrokeJoin和setStrokeMiter。我做过一个进度环,描边默认是圆角连接,结果两端对不齐,调了半天才发现是连接样式的问题。
4.2 抗锯齿:让边缘不再「狗啃」
抗锯齿,说白了就是让斜线、圆弧的边缘更平滑。你想想看,屏幕是像素点阵,斜线如果不做处理,就会出现锯齿状的「阶梯」。
paint.setAntiAlias(true);
就这么一行代码。但有个细节——开启抗锯齿会消耗一点性能。对于静态UI,完全没问题。但如果你在做高频动画(比如60fps的粒子系统),可以考虑关闭抗锯齿,或者用setDither(true)做抖动处理。
我曾经做过一个实时涂鸦App,用户手指滑动时每秒要绘制上百个点。一开始开了抗锯齿,低端机上明显卡顿。后来改成只在最终渲染时开抗锯齿,中间过程关掉,流畅度提升了一大截。
4.3 阴影:给UI加点「立体感」
阴影是提升UI质感的神器。Paint里用setShadowLayer来实现:
paint.setShadowLayer(10f, 5f, 5f, Color.GRAY);
// 参数:模糊半径,x偏移,y偏移,阴影颜色
注意几个坑:
- 必须先关闭硬件加速,否则阴影不显示。可以用
setLayerType(LAYER_TYPE_SOFTWARE, null)。 - 模糊半径不能为0,否则没有模糊效果,就是个纯色偏移。
- 阴影颜色建议用半透明,比如
Color.parseColor("#80000000"),效果更自然。
View.setLayerType配合View.LAYER_TYPE_HARDWARE。
4.4 滤镜:给画笔加个「美颜」
ColorFilter是Paint里一个很强大的功能。它可以在绘制时对颜色做「二次处理」。常用的有三种:
| 滤镜类型 | 作用 | 代码示例 |
|---|---|---|
| LightingColorFilter | 模拟光照效果 | new LightingColorFilter(0xFFFFFFFF, 0x00330000) |
| PorterDuffColorFilter | 颜色混合(叠加、取反等) | new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP) |
| ColorMatrixColorFilter | 颜色矩阵变换(灰度、色调等) | new ColorMatrixColorFilter(colorMatrix) |
我印象最深的是用PorterDuffColorFilter做「水印」效果。当时需要在一张图片上叠加一个半透明的红色水印,直接用SRC_ATOP模式,水印只出现在图片有像素的地方,透明区域自动忽略。效果非常干净。
Paint watermarkPaint = new Paint();
watermarkPaint.setColorFilter(new PorterDuffColorFilter(
Color.parseColor("#80FF0000"),
PorterDuff.Mode.SRC_ATOP
));
canvas.drawBitmap(originalBitmap, 0, 0, watermarkPaint);
4.5 Shader:让画笔「有纹理」
Shader是Paint里另一个重量级角色。它决定了画笔的「填充图案」。Android提供了四种Shader:
- LinearGradient:线性渐变,从A颜色渐变到B颜色
- RadialGradient:径向渐变,从中心向外扩散
- SweepGradient:扫描渐变,像雷达扫描一样旋转
- BitmapShader:用图片作为填充纹理
举个例子,做一个「彩虹进度条」:
Shader shader = new LinearGradient(
0f, 0f, 100f, 0f, // 起点和终点坐标
new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE},
null, // 位置数组,null表示均匀分布
Shader.TileMode.CLAMP // 边缘模式
);
paint.setShader(shader);
canvas.drawRect(0, 0, 300, 50, paint);
这里有个细节——TileMode有三种:
| 模式 | 效果 |
|---|---|
| CLAMP | 边缘颜色向外延伸 |
| REPEAT | 重复渐变图案 |
| MIRROR | 镜像重复(像镜子一样翻转) |
4.6 实战:用Shader做一个「动态波浪」
最后分享一个我项目里用过的案例。当时需要做一个「水位波动」的动画效果,用Shader配合属性动画实现:
// 自定义View的onDraw中
Shader shader = new LinearGradient(
0, 0, getWidth(), 0,
new int[]{0xFF2196F3, 0xFF03A9F4, 0xFF2196F3},
null,
Shader.TileMode.REPEAT
);
paint.setShader(shader);
// 通过属性动画不断改变偏移量
canvas.translate(offsetX, 0);
canvas.drawRect(0, waterLevel, getWidth(), getHeight(), paint);
效果就是水面颜色从左到右渐变,然后通过不断平移实现「流动」的感觉。嗯,这个方案比用Bitmap做纹理要轻量得多,性能也更好。
好了,Paint的核心属性就讲到这里。下一章咱们聊聊Canvas的变换和裁剪——你会发现,配合Paint的这些属性,能画出各种酷炫的效果。