第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会覆盖掉之前设置的ColorFilterShader效果。嗯,这个坑我踩过,后面会细说。

再说样式。Paint有三种样式:

样式 效果 典型场景
FILL 填充内部 实心圆、矩形填充
STROKE 仅描边 空心圆、边框
FILL_AND_STROKE 填充+描边 带边框的实心图形
小技巧:如果你需要描边在填充的「外面」,记得设置setStrokeJoinsetStrokeMiter。我做过一个进度环,描边默认是圆角连接,结果两端对不齐,调了半天才发现是连接样式的问题。

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"),效果更自然。
避坑指南:我曾经在RecyclerView的item里用了阴影,结果滑动时卡成PPT。后来发现是每个item都创建了新的Paint对象。解决方案:把Paint提取成静态变量,或者用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 镜像重复(像镜子一样翻转)
重点:Shader和ColorFilter可以同时使用。但要注意顺序——先设置Shader,再设置ColorFilter。因为ColorFilter是对Shader绘制结果再做一次颜色变换。我见过有人反过来设置,结果滤镜完全没效果。

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的这些属性,能画出各种酷炫的效果。