Paint — 在Android Canvas中对图形进行样式化的主要类。它定义用户界面中所有绘制操作的颜色、线条粗细、填充样式、模糊效果和字体。没有Paint,Canvas无法对显示的元素应用视觉属性。根据Google Developers, 2026,Paint支持多达42种样式参数,包括渐变和纹理。
要点
Paint — 来自android.graphics包的类,存储Canvas上绘制操作的所有样式设置。它不包含像素本身,而是决定线条、形状和文本的外观。每次Canvas方法调用 — drawCircle、drawLine、drawText — 都使用Paint来解析视觉表示。
Paint按照配置对象的原则工作。您创建一个实例,设置一次参数,然后为多个操作重复使用它。这最大限度地减少了绘制循环中的分配数量,与每帧创建新对象相比,性能提升高达30%。根据Google I/O 2024,Paint优化是在自定义图形中实现60 FPS的关键实践之一。
Paint的所有设置分为几个类别:颜色和透明度、描边和填充样式、文本和字体处理、模糊和遮罩效果。每个类别由单独的setter方法管理,可以任意组合使用。这使得Paint API灵活,但需要理解参数的相互作用 — 例如,Style影响Shader是应用于轮廓还是填充。
setColor — 为所有后续绘制操作设置颜色的主要方法。颜色以ARGB格式(32位整数)传递,其中高8位是Alpha通道,其余是红色、绿色和蓝色通道。值0xFF0000FF设置不透明的蓝色。
setAlpha方法允许在0(完全透明)到255(完全不透明)范围内独立调节透明度。与在setColor中指定alpha不同,此方法不影响已设置的颜色色调 — 它仅调节整体透明度。这对于无需改变颜色的元素出现和消失动画非常方便。
ColorFilter — 在绘制之前对每个像素进行颜色变换的机制。支持三种模式:LightingColorFilter用于通道的乘法和加法,PorterDuffColorFilter用于使用混合模式的叠加,ColorMatrixColorFilter用于完整的矩阵变换。ColorFilter在Shader之后、Canvas输出之前应用。
对于大多数任务,标准的setColor或setAlpha就足够了。ColorFilter主要用于去色、棕褐色或反转效果。使用ColorFilter会增加GPU负载,因此Google建议在onDraw的循环调用中最小化其使用。
| 方法 | 描述 | 性能 |
|---|---|---|
| setColor | 设置ARGB颜色 | O(1) — 无额外开销 |
| setAlpha | 调节透明度(0-255) | O(1) — 轻量操作 |
| setColorFilter | 应用色彩校正 | O(n) — 频繁调用时影响FPS |
setStyle方法接受Paint.Style的三个值之一:FILL(填充)、STROKE(轮廓)或FILL_AND_STROKE(同时)。根据样式选择,图形将显示为剪影、骨架或带内部填充的轮廓。默认情况下,Paint以FILL样式创建。
setStrokeWidth以像素为单位设置STROKE模式下的线条粗细。对于圆形形状和圆角,使用setStrokeCap(Cap.ROUND、BUTT、SQUARE)和setStrokeJoin(Join.MITER、ROUND、BEVEL)。这些参数对自定义View中图表、路径和边框的外观有重要影响。
在小于1像素的粗细下,线条以亚像素精度绘制,但在低密度屏幕上可能看起来模糊。对于Android Retina显示屏,使用TypedValue.applyDimension将dp转换为像素。2 dp的粗细在大多数设备上提供清晰的可见性。
DashPathEffect创建点划线和虚线。构造函数接受交替线段和间隔长度的数组以及初始相位偏移。例如,数组[10, 5, 3, 5]创建一条线段、短间隔、短线段和间隔的线条。该效果在图形编辑器和注释工具中广泛使用。
样式组合通过setPathEffect实现。通过组合CornerPathEffect、DashPathEffect和DiscretePathEffect,可以为图表、标题和装饰元素创建复杂的视觉线条。每个PathEffect在CPU上计算,因此在包含大量线条的动画中,建议预先将效果栅格化为Bitmap。
val paint = Paint().apply {
setStyle(Paint.Style.STROKE)
setStrokeWidth(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics))
setPathEffect(DashPathEffect(floatArrayOf(10f, 5f, 3f, 5f), 0f))
}
Typeface — 决定Paint所有文本操作字体的对象。Android支持系统字体(Roboto、Noto Sans)、assets资源中的自定义字体以及通过Downloadable Fonts API下载的字体。Typeface的设置通过setTypeface在Paint实例上完成。
setTextSize方法以像素为单位设置字体大小。为了在不同屏幕上正确显示,建议使用TypedValue.applyDimension以sp为单位指定大小。行间距通过setTextScaleX进行水平拉伸调节,并包含在通过Canvas.drawText的MultiLine文本内置支持中。
文本对齐使用setTextAlign,值包括LEFT、CENTER和RIGHT。在CENTER模式下,传递给drawText的x坐标成为文本的中心点 — 这对于图标下方的标题和形状内的标题特别方便。精确定位使用measureText和getTextBounds方法,它们返回文本的宽度和边界矩形。
setUnderlineText和setStrikeThruText分别启用下划线和删除线。这些参数在字体级别工作,并应用于由该Paint绘制的所有文本。要灵活格式化同一行的不同部分,请使用具有不同设置的单独Paint对象,或在drawText调用之间切换参数。
| 方法 | 用途 | 计量单位 |
|---|---|---|
| setTextSize | 字体大小 | px(像素) |
| setTextAlign | 水平对齐 | LEFT, CENTER, RIGHT |
| setTypeface | 字体 | Typeface object |
| setUnderlineText | 下划线 | Boolean |
Shader — 用渐变、位图纹理和合成效果填充形状的基类。通过setShader方法可以分配任何子类:LinearGradient、RadialGradient、SweepGradient、BitmapShader或ComposeShader。Shader替换由setColor设置的颜色,并应用于所有绘制的元素。
LinearGradient沿直线在两种或多种颜色之间创建平滑过渡。构造函数接受起点和终点、颜色数组及其位置(平铺模式)。平铺模式决定渐变之外的行为:CLAMP(重复最后一种颜色)、MIRROR(镜像)或REPEAT(重复)。LinearGradient广泛用于背景填充和进度条。
RadialGradient创建从中心到边缘的圆形渐变。中心由坐标定义,半径确定颜色过渡区域。径向渐变的外观类似于点光源照明 — 这常用于模拟按钮发光和活动元素的背光效果。
BlurMaskFilter在绘制元素周围添加模糊效果。构造函数接受模糊半径和样式:NORMAL(所有方向模糊)、INNER(仅在形状内部)、OUTER(仅外部)和SOLID(形状加外部模糊)。BlurMaskFilter适用于任何类型的Paint,常用于创建阴影和发光效果。
val paint = Paint().apply {
setShader(LinearGradient(
0f, 0f, width, height,
intArrayOf(Color.RED, Color.BLUE),
null,
Shader.TileMode.CLAMP
))
setMaskFilter(BlurMaskFilter(20f, BlurMaskFilter.Blur.NORMAL))
}
让我们看一个完整的自定义View示例,它绘制一个带有渐变填充和文本标注的圆。Paint和Canvas的组合使得能够用最少的代码创建复杂图形。所有设置已移至初始化以提高性能。
class GradientCircleView@JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
private val fillPaint = Paint().apply {
setShader(RadialGradient(
0f, 0f, 200f,
intArrayOf(Color.CYAN, Color.DKGRAY),
null, Shader.TileMode.CLAMP
))
}
private val textPaint = Paint().apply {
setColor(Color.WHITE)
setTextSize(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP, 24f, resources.displayMetrics
))
setTextAlign(Paint.Align.CENTER)
setMaskFilter(BlurMaskFilter(5f, BlurMaskFilter.Blur.NORMAL))
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(200f, 200f, 150f, fillPaint)
canvas.drawText("渐变", 200f, 210f, textPaint)
}
}
在代码中使用了两个独立的Paint对象:一个用于用径向渐变填充圆,另一个用于通过BlurMaskFilter设置带阴影的文本。这种分离允许独立配置参数,避免在不同类型元素绘制之间重置Paint状态。为了提高性能,两个Paint都在构造函数中创建,而不是在onDraw中。
在不同像素密度的设备上缩放时,尺寸值必须通过TypedValue.applyDimension重新计算。否则,文本和渐变在mdpi和xxxhdpi屏幕上会显示不同。在示例中,文本大小以sp设置,会自动适应用户的系统字体大小设置。
常见问题
Canvas执行绘制操作(drawCircle、drawLine),而Paint确定样式 — 颜色、粗细、字体。Canvas不包含样式设置,因此没有Paint,任何绘制操作都没有视觉效果。
是的,一个Paint对象可以重复用于多个形状。建议为不同类型的元素(背景、文本、轮廓)创建单独的Paint,但不是为每个形状 — 这减少了垃圾回收器的负载。
reset方法将Paint恢复到与新实例等效的状态。或者,可以使用带标志的set,但reset更有利于代码可读性。重置后,所有样式参数采用默认值。
在onDraw内部创建Paint会在每帧造成分配,导致GC暂停。所有Paint对象应在View的构造函数中创建。通过setter在onDraw中更改参数是安全的,不会创建新对象。
Android中的文本始终以透明背景绘制。drawText方法仅显示字形,没有背景矩形。如果文本下方需要背景,先用单独的Paint通过drawRoundRect绘制矩形,然后再绘制文本。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。