Android Canvas绘图高级技巧:从基础到特效实现
本文深入探讨Android Canvas的高级绘图技巧与应用,从基础回顾到高级特性全面解析。首先介绍Canvas核心组件(Canvas、Paint、Bitmap、Path)及基本绘图操作,包括几何图形、文本和图像的绘制方法。重点讲解高级路径应用,如贝塞尔曲线绘制复杂图形、路径运算和文字沿路径排列等技巧。文章还涵盖坐标系变换、状态保存与恢复等关键概念,通过代码示例展示如何实现心形路径等复杂图形绘制。
一、引言
在Android开发中,Canvas(画布)是2D图形绘制的核心,它为开发者提供了丰富的绘图API。从简单的几何图形到复杂的自定义控件,从静态图表到动态特效,Canvas几乎无处不在。然而,很多开发者仅仅停留在使用Canvas绘制基本图形的层面,对于其高级特性和性能优化了解甚少。
本文将从Canvas的基础知识出发,逐步深入到高级绘图技巧和特效实现。我们将探讨Canvas的核心组件、高级绘图技巧、性能优化方法以及如何实现各种炫酷的视觉效果。无论你是刚接触Canvas的新手,还是有一定经验想要深入学习的开发者,本文都将为你提供实用的知识和技巧。
为什么需要掌握Canvas高级技巧?
- 自定义UI需求:当系统提供的控件无法满足设计需求时,自定义View成为唯一选择
- 性能优化:不当的Canvas使用会导致性能问题,影响应用流畅度
- 视觉效果提升:高级绘图技巧可以实现更丰富的视觉效果,提升用户体验
- 游戏和图形应用开发:Canvas是2D游戏和图形应用的基础
二、Canvas绘图基础回顾
2.1 Canvas核心组件
Canvas类:绘图画布
Canvas是绘图的载体,提供了各种绘制方法。需要注意的是,Canvas本身并不存储绘制的图形,它更像是一个"绘制指令"的执行者。
// Canvas的基本使用
Canvas canvas = new Canvas(bitmap); // 使用Bitmap作为画布
canvas.drawColor(Color.WHITE); // 设置画布背景色
Paint类:画笔与样式控制
Paint控制绘制的样式,包括颜色、粗细、字体、特效等。正确配置Paint是高效绘图的关键。
// Paint的详细配置
Paint paint = new Paint();
paint.setColor(Color.RED); // 设置颜色
paint.setStyle(Paint.Style.FILL); // 填充样式
paint.setStrokeWidth(5); // 线条宽度
paint.setAntiAlias(true); // 开启抗锯齿
paint.setTextSize(36); // 文字大小
paint.setAlpha(128); // 设置透明度(0-255)
Bitmap类:图像载体
Bitmap是Canvas绘图的最终载体,也是离屏缓冲的基础。
// 创建Bitmap的几种方式
// 1. 创建空Bitmap
Bitmap bitmap = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_8888);
// 2. 从资源加载
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
// 3. 从文件加载
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/image.jpg");
Path类:路径绘制
Path用于绘制复杂的图形,支持直线、曲线和组合图形。
// Path的基本使用
Path path = new Path();
path.moveTo(100, 100); // 移动到起点
path.lineTo(200, 100); // 画直线
path.quadTo(250, 50, 300, 100); // 画二次贝塞尔曲线
path.close(); // 闭合路径
canvas.drawPath(path, paint);
2.2 基本绘图操作
几何图形绘制
// 矩形
canvas.drawRect(100, 100, 300, 300, paint);
// 圆角矩形
canvas.drawRoundRect(100, 100, 300, 300, 20, 20, paint);
// 圆形
canvas.drawCircle(200, 200, 100, paint);
// 椭圆
canvas.drawOval(100, 100, 300, 200, paint);
// 弧形
canvas.drawArc(100, 100, 300, 300, 0, 90, true, paint);
文本绘制
// 基本文本绘制
canvas.drawText("Hello Canvas", 100, 100, paint);
// 获取文本尺寸
Rect bounds = new Rect();
paint.getTextBounds("Hello", 0, "Hello".length(), bounds);
float textWidth = paint.measureText("Hello");
float textHeight = bounds.height();
// 多行文本绘制
String text = "This is a multi-line\ntext example";
StaticLayout layout = new StaticLayout(
text, paint, canvas.getWidth(),
Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
layout.draw(canvas);
图像绘制
// 基本图像绘制
canvas.drawBitmap(bitmap, 100, 100, paint);
// 缩放绘制
Rect src = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Rect dst = new Rect(100, 100, 500, 400);
canvas.drawBitmap(bitmap, src, dst, paint);
2.3 坐标系与变换基础
屏幕坐标系
Android的Canvas使用左上角为原点的坐标系,X轴向右为正,Y轴向下为正。
// 坐标系变换示例
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 保存当前画布状态
canvas.save();
// 将原点移动到视图中心
canvas.translate(getWidth()/2f, getHeight()/2f);
// 旋转45度
canvas.rotate(45);
// 绘制一个矩形,现在会围绕视图中心旋转
canvas.drawRect(-100, -100, 100, 100, paint);
// 恢复画布状态
canvas.restore();
}
变换的保存与恢复
Canvas的变换操作是累积的,使用save()和restore()管理状态栈非常重要。
canvas.save(); // 保存状态1
canvas.translate(100, 100);
canvas.drawRect(0, 0, 50, 50, paint); // 在(100,100)处绘制
canvas.save(); // 保存状态2
canvas.rotate(45);
canvas.drawRect(0, 0, 50, 50, paint); // 旋转45度绘制
canvas.restore(); // 恢复到状态2(平移后的状态)
canvas.drawRect(50, 0, 100, 50, paint); // 在(150,100)处绘制
canvas.restore(); // 恢复到状态1(原始状态)
三、Canvas高级绘图技巧
3.1 路径(Path)高级应用
贝塞尔曲线绘制复杂图形
贝塞尔曲线是绘制平滑曲线的关键工具,Android支持二次和三次贝塞尔曲线。
// 心形路径绘制
private Path createHeartPath(float width, float height) {
Path path = new Path();
// 从底部尖端开始
path.moveTo(width / 2, height);
// 绘制左侧曲线
path.cubicTo(
width / 8, height * 3 / 4, // 控制点1
width / 8, height / 4, // 控制点2
width / 2, height / 4 // 结束点
);
// 绘制右侧曲线
path.cubicTo(
width * 7 / 8, height / 4, // 控制点1
width * 7 / 8, height * 3 / 4, // 控制点2
width / 2, height // 结束点
);
path.close();
return path;
}
路径运算
Android P及以上版本支持路径布尔运算,可以创建复杂的组合图形。
// 路径运算示例
@RequiresApi(api = Build.VERSION_CODES.P)
private Path createCompositePath() {
// 创建两个圆形路径
Path circle1 = new Path();
circle1.addCircle(200, 200, 100, Path.Direction.CW);
Path circle2 = new Path();
circle2.addCircle(300, 200, 100, Path.Direction.CW);
// 执行并集运算
circle1.op(circle2, Path.Op.UNION);
// 其他运算类型:
// Op.INTERSECT - 交集
// Op.DIFFERENCE - 差集
// Op.REVERSE_DIFFERENCE - 反向差集
// Op.XOR - 异或
return circle1;
}
路径测量与文字沿路径排列
PathMeasure可以精确测量路径长度和位置,常用于进度指示器和路径动画。
// 路径测量和文字沿路径排列
private void drawTextAlongPath(Canvas canvas, String text, Path path) {
PathMeasure pathMeasure = new PathMeasure(path, false);
float pathLength = pathMeasure.getLength();
// 计算文字总长度
float textWidth = paint.measureText(text);
// 计算起始偏移,使文字居中
float offset = (pathLength - textWidth) / 2;
// 绘制文字沿路径
canvas.drawTextOnPath(text, path, offset, 0, paint);
// 获取路径上的点
float[] pos = new float[2];
float[] tan = new float[2];
// 获取路径中点位置和切线角度
pathMeasure.getPosTan(pathLength / 2, pos, tan);
float angle = (float) (Math.atan2(tan[1], tan[0]) * 180 / Math.PI);
// 在路径中点绘制标记
canvas.save();
canvas.translate(pos[0], pos[1]);
canvas.rotate(angle);
canvas.drawCircle(0, 0, 10, paint);
canvas.restore();
}
路径特效
DashPathEffect可以创建虚线、点线等效果。
// 虚线效果
private void drawDashedPath(Canvas canvas) {
Paint dashedPaint = new Paint(paint);
// 创建虚线效果:实线10px,间隔5px
DashPathEffect dashEffect = new DashPathEffect(
new float[]{10, 5}, 0);
dashedPaint.setPathEffect(dashEffect);
// 创建相位动画的虚线
float phase = (System.currentTimeMillis() / 50) % 15;
DashPathEffect animatedDashEffect = new DashPathEffect(
new float[]{10, 5}, phase);
// 复合路径效果:先虚线再模糊
PathEffect pathEffect = new ComposePathEffect(
animatedDashEffect,
new CornerPathEffect(20) // 圆角效果
);
dashedPaint.setPathEffect(pathEffect);
// 绘制路径
Path path = new Path();
path.moveTo(100, 100);
path.lineTo(500, 100);
path.lineTo(500, 500);
path.lineTo(100, 500);
path.close();
canvas.drawPath(path, dashedPaint);
}
3.2 画笔(Paint)高级配置
3.2.1 着色器(Shader)深度应用
着色器定义了图形的填充方式,可以创建渐变、纹理等效果。
// 线性渐变
private Shader createLinearGradient() {
return new LinearGradient(
0, 0, // 起始点
getWidth(), getHeight(), // 结束点
Color.RED, // 起始颜色
Color.BLUE, // 结束颜色
Shader.TileMode.CLAMP // 边缘处理模式
);
}
// 径向渐变
private Shader createRadialGradient() {
return new RadialGradient(
getWidth() / 2f, // 圆心X
getHeight() / 2f, // 圆心Y
Math.min(getWidth(), getHeight()) / 2f, // 半径
Color.YELLOW, // 中心颜色
Color.TRANSPARENT, // 边缘颜色
Shader.TileMode.CLAMP
);
}
// 扫描渐变(环形渐变)
private Shader createSweepGradient() {
return new SweepGradient(
getWidth() / 2f,
getHeight() / 2f,
new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE, Color.RED},
new float[]{0, 0.25f, 0.5f, 0.75f, 1}
);
}
// 位图着色器
private Shader createBitmapShader(Bitmap bitmap) {
return new BitmapShader(
bitmap,
Shader.TileMode.REPEAT, // X方向平铺模式
Shader.TileMode.MIRROR // Y方向镜像模式
);
}
// 组合着色器
private Shader createComposeShader() {
Shader gradient = new LinearGradient(0, 0, 0, getHeight(),
Color.RED, Color.BLUE, Shader.TileMode.CLAMP);
Bitmap patternBitmap = BitmapFactory.decodeResource(
getResources(), R.drawable.pattern);
Shader bitmapShader = new BitmapShader(patternBitmap,
Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
// 使用PorterDuff混合模式组合着色器
return new ComposeShader(gradient, bitmapShader,
PorterDuff.Mode.MULTIPLY);
}
3.2.2 颜色过滤器(ColorFilter)
颜色过滤器可以修改绘图的颜色,实现各种滤镜效果。
// LightingColorFilter - 模拟光照效果
private ColorFilter createLightingFilter() {
// 参数1: 乘以RGB值 (0xRRGGBB)
// 参数2: 加上RGB值 (0xRRGGBB)
return new LightingColorFilter(0xFF808080, 0x00000000);
}
// PorterDuffColorFilter - 使用PorterDuff模式混合颜色
private ColorFilter createPorterDuffFilter() {
return new PorterDuffColorFilter(
Color.argb(150, 255, 0, 0), // 红色,150透明度
PorterDuff.Mode.SRC_ATOP
);
}
// ColorMatrixColorFilter - 强大的矩阵变换
private ColorFilter createColorMatrixFilter() {
ColorMatrix colorMatrix = new ColorMatrix();
// 1. 黑白效果(去饱和度)
// colorMatrix.setSaturation(0);
// 2. 复古效果(棕褐色)
float[] sepia = {
0.393f, 0.769f, 0.189f, 0, 0,
0.349f, 0.686f, 0.168f, 0, 0,
0.272f, 0.534f, 0.131f, 0, 0,
0, 0, 0, 1, 0
};
colorMatrix.set(sepia);
// 3. 亮度调整
colorMatrix.postConcat(new ColorMatrix(new float[]{
1, 0, 0, 0, 50, // 红色通道加50
0, 1, 0, 0, 50, // 绿色通道加50
0, 0, 1, 0, 50, // 蓝色通道加50
0, 0, 0, 1, 0
}));
return new ColorMatrixColorFilter(colorMatrix);
}
3.2.3 特效与遮罩
// 模糊效果
private MaskFilter createBlurMaskFilter() {
// 参数:模糊半径,模糊类型
return new BlurMaskFilter(20, BlurMaskFilter.Blur.NORMAL);
// 其他模糊类型:
// BlurMaskFilter.Blur.SOLID - 内部模糊,外部正常绘制
// BlurMaskFilter.Blur.OUTER - 只模糊外部
// BlurMaskFilter.Blur.INNER - 只模糊内部
}
// 浮雕效果
private MaskFilter createEmbossMaskFilter() {
// 参数:光源方向,环境光,镜面光,模糊半径
float[] direction = {1, 1, 1}; // 光源方向向量
float ambient = 0.4f; // 环境光强度
float specular = 0.6f; // 镜面光强度
float blurRadius = 3.0f; // 模糊半径
return new EmbossMaskFilter(direction, ambient, specular, blurRadius);
}
3.3 图层(Layer)与混合模式
3.3.1 图层保存与恢复
图层是Canvas绘图的重要概念,允许在独立区域进行绘制操作。
// 使用saveLayer创建透明图层
private void drawWithLayer(Canvas canvas) {
int saveCount = canvas.saveLayer(
0, 0, getWidth(), getHeight(), // 图层边界
null, // 使用默认Paint
Canvas.ALL_SAVE_FLAG // 保存所有状态
);
try {
// 在图层上绘制半透明矩形
Paint layerPaint = new Paint();
layerPaint.setColor(Color.argb(128, 255, 0, 0));
canvas.drawRect(100, 100, 300, 300, layerPaint);
// 绘制其他内容...
} finally {
// 恢复画布状态,将图层内容合成到主画布
canvas.restoreToCount(saveCount);
}
}
// 带Alpha通道的图层
private void drawWithAlphaLayer(Canvas canvas) {
Paint alphaPaint = new Paint();
alphaPaint.setAlpha(128); // 设置图层透明度
int layerId = canvas.saveLayerAlpha(
0, 0, getWidth(), getHeight(),
128, // 图层整体透明度
Canvas.ALL_SAVE_FLAG
);
// 在图层内绘制
canvas.drawCircle(200, 200, 100, paint);
canvas.restoreToCount(layerId);
}
3.3.2 PorterDuff混合模式详解
PorterDuff混合模式定义了源图像(当前绘制)和目标图像(已存在内容)如何混合。
// PorterDuff混合模式示例
private void demonstratePorterDuffModes(Canvas canvas) {
Paint paint = new Paint();
// 先绘制目标图像(蓝色矩形)
paint.setColor(Color.BLUE);
canvas.drawRect(100, 100, 300, 300, paint);
// 设置混合模式
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 绘制源图像(红色圆形)
paint.setColor(Color.RED);
canvas.drawCircle(200, 200, 100, paint);
// 清除混合模式
paint.setXfermode(null);
}
// 常用混合模式实际应用
private void practicalPorterDuffExamples(Canvas canvas) {
// 1. 实现圆形头像(使用DST_IN)
Bitmap avatar = getAvatarBitmap();
int centerX = getWidth() / 2;
int centerY = getHeight() / 2;
int radius = 100;
// 先绘制圆形遮罩
Paint maskPaint = new Paint();
canvas.drawCircle(centerX, centerY, radius, maskPaint);
// 设置DST_IN模式,只显示圆形区域内的头像
maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(avatar,
centerX - avatar.getWidth() / 2,
centerY - avatar.getHeight() / 2,
maskPaint);
maskPaint.setXfermode(null);
// 2. 实现橡皮擦效果(使用CLEAR)
Paint eraserPaint = new Paint();
eraserPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
eraserPaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(touchX, touchY, eraserRadius, eraserPaint);
// 3. 实现叠加效果(使用MULTIPLY)
Paint multiplyPaint = new Paint();
multiplyPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
multiplyPaint.setColor(Color.argb(128, 255, 0, 0)); // 半透明红色
canvas.drawRect(rect, multiplyPaint);
}
3.4 矩阵(Matrix)变换高级技巧
自定义矩阵变换
// 使用Matrix进行复杂变换
private void applyCustomMatrix(Canvas canvas) {
Matrix matrix = new Matrix();
// 1. 组合变换
matrix.postTranslate(100, 100); // 平移
matrix.postRotate(45); // 旋转(以原点为中心)
matrix.postScale(1.5f, 1.5f); // 缩放
// 2. 自定义矩阵
float[] values = {
1, 0, 100, // X轴变换
0, 1, 100, // Y轴变换
0, 0, 1 // 透视变换
};
matrix.setValues(values);
// 3. 应用矩阵到Canvas
canvas.save();
canvas.concat(matrix);
// 绘制内容
canvas.drawBitmap(bitmap, 0, 0, paint);
canvas.restore();
}
// 3D透视效果
private void apply3DPerspective(Canvas canvas) {
Matrix matrix = new Matrix();
// 创建3D透视变换
float[] src = {
0, 0, // 左上
bitmap.getWidth(), 0, // 右上
bitmap.getWidth(), bitmap.getHeight(), // 右下
0, bitmap.getHeight() // 左下
};
float[] dst = {
50, 50, // 左上(向右下偏移)
bitmap.getWidth() - 50, 0, // 右上(向左上偏移)
bitmap.getWidth(), bitmap.getHeight(), // 右下不变
0, bitmap.getHeight() - 50 // 左下(向右上偏移)
};
// 计算透视变换矩阵
matrix.setPolyToPoly(src, 0, dst, 0, 4);
canvas.save();
canvas.concat(matrix);
canvas.drawBitmap(bitmap, 0, 0, paint);
canvas.restore();
}
多点触控变换实现
// 多点触控变换(缩放、旋转、平移)
public class MultiTouchTransformer {
private Matrix currentMatrix = new Matrix();
private Matrix savedMatrix = new Matrix();
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;
private PointF startPoint = new PointF();
private float startDistance = 1f;
private float startAngle = 0f;
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mode = DRAG;
startPoint.set(event.getX(), event.getY());
savedMatrix.set(currentMatrix);
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode = ZOOM;
startDistance = getDistance(event);
startAngle = getAngle(event);
savedMatrix.set(currentMatrix);
break;
case MotionEvent.ACTION_MOVE:
if (mode == DRAG) {
currentMatrix.set(savedMatrix);
float dx = event.getX() - startPoint.x;
float dy = event.getY() - startPoint.y;
currentMatrix.postTranslate(dx, dy);
} else if (mode == ZOOM && event.getPointerCount() == 2) {
float newDistance = getDistance(event);
float newAngle = getAngle(event);
if (newDistance > 10f) {
float scale = newDistance / startDistance;
currentMatrix.set(savedMatrix);
currentMatrix.postScale(scale, scale,
getMidPoint(event).x, getMidPoint(event).y);
}
float rotate = newAngle - startAngle;
currentMatrix.postRotate(rotate,
getMidPoint(event).x, getMidPoint(event).y);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mode = NONE;
break;
}
return true;
}
private float getDistance(MotionEvent event) {
float dx = event.getX(0) - event.getX(1);
float dy = event.getY(0) - event.getY(1);
return (float) Math.sqrt(dx * dx + dy * dy);
}
private PointF getMidPoint(MotionEvent event) {
return new PointF(
(event.getX(0) + event.getX(1)) / 2,
(event.getY(0) + event.getY(1)) / 2
);
}
private float getAngle(MotionEvent event) {
double delta_x = event.getX(0) - event.getX(1);
double delta_y = event.getY(0) - event.getY(1);
return (float) Math.toDegrees(Math.atan2(delta_y, delta_x));
}
public Matrix getCurrentMatrix() {
return currentMatrix;
}
}
四、Canvas性能优化
4.1 绘图性能瓶颈分析
过度绘制检测与优化
过度绘制是指同一像素被多次绘制的现象,会严重影响性能。
// 检测过度绘制的方法
public class DrawingOptimizationView extends View {
private Paint debugPaint;
private boolean showOverdraw = false;
public DrawingOptimizationView(Context context) {
super(context);
init();
}
private void init() {
// 开启过度绘制调试
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// 红色:绘制了4次以上
// 粉色:绘制了3次
// 绿色:绘制了2次
// 蓝色:绘制了1次
// 没有颜色:没有绘制
setLayerType(LAYER_TYPE_HARDWARE, null);
}
debugPaint = new Paint();
debugPaint.setColor(Color.RED);
debugPaint.setStyle(Paint.Style.FILL);
debugPaint.setAlpha(50); // 半透明
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (showOverdraw) {
// 使用半透明绘制来模拟过度绘制
for (int i = 0; i < 5; i++) {
canvas.drawRect(100 + i * 10, 100 + i * 10,
300 + i * 10, 300 + i * 10, debugPaint);
}
}
// 优化后的绘制代码
optimizedDrawing(canvas);
}
private void optimizedDrawing(Canvas canvas) {
// 1. 避免绘制不可见区域
Rect visibleRect = new Rect();
getLocalVisibleRect(visibleRect);
// 2. 只绘制可见部分
if (needDrawContent(visibleRect)) {
drawContent(canvas, visibleRect);
}
}
private boolean needDrawContent(Rect visibleRect) {
// 检查内容是否在可见区域内
return visibleRect.intersect(contentRect);
}
}
内存占用分析
Canvas绘图中的内存问题主要来自Bitmap和频繁的对象创建。
// 内存优化示例
public class MemoryOptimizedView extends View {
private Paint paint;
private Bitmap cachedBitmap; // 缓存绘制结果
private boolean isDirty = true; // 脏标记
public MemoryOptimizedView(Context context) {
super(context);
init();
}
private void init() {
// 重用Paint对象
paint = new Paint();
paint.setAntiAlias(true);
// 设置合适的Bitmap配置
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888; // 高质量
// options.inPreferredConfig = Bitmap.Config.RGB_565; // 低内存
// 根据View大小调整Bitmap采样率
options.inSampleSize = calculateSampleSize();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 释放旧的Bitmap
if (cachedBitmap != null) {
cachedBitmap.recycle();
cachedBitmap = null;
}
// 创建新的缓存Bitmap
if (w > 0 && h > 0) {
cachedBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
isDirty = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 如果内容已改变,重新绘制到缓存Bitmap
if (isDirty && cachedBitmap != null) {
Canvas cacheCanvas = new Canvas(cachedBitmap);
drawToCache(cacheCanvas);
isDirty = false;
}
// 从缓存Bitmap绘制到屏幕
if (cachedBitmap != null && !cachedBitmap.isRecycled()) {
canvas.drawBitmap(cachedBitmap, 0, 0, paint);
}
}
private void drawToCache(Canvas canvas) {
// 复杂的绘制操作
canvas.drawColor(Color.WHITE);
// ... 其他绘制操作
}
public void markDirty() {
isDirty = true;
invalidate();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
// 释放资源
if (cachedBitmap != null) {
cachedBitmap.recycle();
cachedBitmap = null;
}
}
}
4.2 高效绘图策略
脏矩形更新技术
只更新需要重绘的区域,可以显著提高性能。
// 脏矩形更新实现
public class DirtyRectView extends View {
private Rect dirtyRect = new Rect();
private boolean fullRedraw = true;
@Override
public void invalidate() {
fullRedraw = true;
super.invalidate();
}
@Override
public void invalidate(Rect dirty) {
if (fullRedraw) {
super.invalidate();
} else {
// 合并脏矩形
dirtyRect.union(dirty);
super.invalidate(dirtyRect);
}
}
@Override
public void invalidate(int l, int t, int r, int b) {
if (fullRedraw) {
super.invalidate();
} else {
dirtyRect.union(l, t, r, b);
super.invalidate(dirtyRect);
}
}
@Override
protected void onDraw(Canvas canvas) {
if (fullRedraw) {
// 完整重绘
drawFull(canvas);
fullRedraw = false;
dirtyRect.setEmpty();
} else if (!dirtyRect.isEmpty()) {
// 部分重绘
canvas.save();
canvas.clipRect(dirtyRect);
drawPartial(canvas, dirtyRect);
canvas.restore();
dirtyRect.setEmpty();
}
}
private void drawFull(Canvas canvas) {
// 完整绘制逻辑
canvas.drawColor(Color.WHITE);
// ... 绘制所有内容
}
private void drawPartial(Canvas canvas, Rect dirtyRect) {
// 只绘制脏矩形区域内的内容
// 需要实现智能的脏区域绘制逻辑
Iterator<DrawableItem> iterator = items.iterator();
while (iterator.hasNext()) {
DrawableItem item = iterator.next();
if (Rect.intersects(dirtyRect, item.getBounds())) {
item.draw(canvas);
}
}
}
}
离屏缓冲优化
// 双重缓冲技术
public class DoubleBufferedView extends View {
private Bitmap bufferBitmap;
private Canvas bufferCanvas;
private final Object bufferLock = new Object();
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
synchronized (bufferLock) {
if (bufferBitmap != null) {
bufferBitmap.recycle();
}
if (w > 0 && h > 0) {
bufferBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
bufferCanvas = new Canvas(bufferBitmap);
}
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
synchronized (bufferLock) {
if (bufferBitmap != null && !bufferBitmap.isRecycled()) {
// 先绘制到缓冲Canvas
drawToBuffer(bufferCanvas);
// 然后将缓冲Bitmap绘制到屏幕Canvas
canvas.drawBitmap(bufferBitmap, 0, 0, null);
}
}
}
private void drawToBuffer(Canvas canvas) {
long startTime = System.currentTimeMillis();
// 复杂的绘制操作
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// 绘制背景
Paint bgPaint = new Paint();
bgPaint.setShader(new LinearGradient(0, 0, getWidth(), getHeight(),
Color.WHITE, Color.LTGRAY, Shader.TileMode.CLAMP));
canvas.drawRect(0, 0, getWidth(), getHeight(), bgPaint);
// 绘制其他内容...
long drawTime = System.currentTimeMillis() - startTime;
if (drawTime > 16) { // 超过16ms,警告可能掉帧
Log.w("DoubleBufferedView", "Draw time too long: " + drawTime + "ms");
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
synchronized (bufferLock) {
if (bufferBitmap != null) {
bufferBitmap.recycle();
bufferBitmap = null;
bufferCanvas = null;
}
}
}
}
4.3 大图处理与分块绘制
// 大图分块绘制
public class LargeImageTileView extends View {
private Bitmap largeBitmap;
private Rect viewport = new Rect();
private List<BitmapTile> visibleTiles = new ArrayList<>();
class BitmapTile {
Rect srcRect;
Rect dstRect;
Bitmap tileBitmap;
BitmapTile(Rect src, Rect dst, Bitmap bitmap) {
this.srcRect = src;
this.dstRect = dst;
this.tileBitmap = bitmap;
}
}
public void setLargeBitmap(Bitmap bitmap) {
this.largeBitmap = bitmap;
calculateVisibleTiles();
invalidate();
}
private void calculateVisibleTiles() {
visibleTiles.clear();
if (largeBitmap == null) return;
int tileSize = 512; // 瓦片大小
int bitmapWidth = largeBitmap.getWidth();
int bitmapHeight = largeBitmap.getHeight();
// 计算当前视口对应的位图区域
Rect bitmapVisibleRect = viewportToBitmapRect(viewport);
// 计算需要加载的瓦片
int startX = (bitmapVisibleRect.left / tileSize) * tileSize;
int startY = (bitmapVisibleRect.top / tileSize) * tileSize;
for (int y = startY; y < bitmapVisibleRect.bottom; y += tileSize) {
for (int x = startX; x < bitmapVisibleRect.right; x += tileSize) {
Rect srcRect = new Rect(
x, y,
Math.min(x + tileSize, bitmapWidth),
Math.min(y + tileSize, bitmapHeight)
);
Rect dstRect = bitmapToViewportRect(srcRect);
// 异步加载瓦片
loadTileAsync(srcRect, dstRect);
}
}
}
private Rect viewportToBitmapRect(Rect viewport) {
// 将视口坐标转换为位图坐标
// 这里需要根据缩放比例和偏移量计算
float scale = getCurrentScale();
float offsetX = getOffsetX();
float offsetY = getOffsetY();
return new Rect(
(int) ((viewport.left - offsetX) / scale),
(int) ((viewport.top - offsetY) / scale),
(int) ((viewport.right - offsetX) / scale),
(int) ((viewport.bottom - offsetY) / scale)
);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制可见的瓦片
for (BitmapTile tile : visibleTiles) {
if (tile.tileBitmap != null && !tile.tileBitmap.isRecycled()) {
canvas.drawBitmap(tile.tileBitmap, tile.srcRect, tile.dstRect, null);
} else {
// 绘制占位符
Paint placeholderPaint = new Paint();
placeholderPaint.setColor(Color.LTGRAY);
canvas.drawRect(tile.dstRect, placeholderPaint);
}
}
// 绘制加载中的提示
drawLoadingIndicators(canvas);
}
private void loadTileAsync(final Rect srcRect, final Rect dstRect) {
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
// 从大图中提取瓦片
return Bitmap.createBitmap(
largeBitmap,
srcRect.left, srcRect.top,
srcRect.width(), srcRect.height()
);
}
@Override
protected void onPostExecute(Bitmap tileBitmap) {
visibleTiles.add(new BitmapTile(
new Rect(0, 0, tileBitmap.getWidth(), tileBitmap.getHeight()),
dstRect,
tileBitmap
));
invalidate(dstRect);
}
}.execute();
}
}
五、Canvas特效实现
5.1 2D图形特效
5.1.1 粒子系统实现
// 完整粒子系统实现
public class ParticleSystem {
private List<Particle> particles = new ArrayList<>();
private Random random = new Random();
private Paint particlePaint = new Paint();
class Particle {
float x, y; // 位置
float vx, vy; // 速度
float ax, ay; // 加速度
float radius; // 半径
int color; // 颜色
float life; // 生命周期 (0-1)
float decay; // 衰减速度
float rotation; // 旋转角度
float rotationSpeed; // 旋转速度
void update(float dt) {
// 更新速度
vx += ax * dt;
vy += ay * dt;
// 更新位置
x += vx * dt;
y += vy * dt;
// 更新生命周期
life -= decay * dt;
// 更新旋转
rotation += rotationSpeed * dt;
// 边界检测
if (x < 0 || x > screenWidth) vx = -vx;
if (y < 0 || y > screenHeight) vy = -vy;
}
boolean isAlive() {
return life > 0;
}
}
public void createExplosion(float centerX, float centerY, int count) {
for (int i = 0; i < count; i++) {
Particle p = new Particle();
p.x = centerX;
p.y = centerY;
// 随机方向
float angle = random.nextFloat() * (float) Math.PI * 2;
float speed = 100 + random.nextFloat() * 200;
p.vx = (float) Math.cos(angle) * speed;
p.vy = (float) Math.sin(angle) * speed;
// 重力
p.ay = 300;
// 随机大小和颜色
p.radius = 2 + random.nextFloat() * 6;
p.color = Color.argb(255,
255, // 红色
100 + random.nextInt(155), // 绿色
random.nextInt(100) // 蓝色
);
p.life = 1.0f;
p.decay = 0.5f + random.nextFloat() * 1.0f;
p.rotation = random.nextFloat() * 360;
p.rotationSpeed = (random.nextFloat() - 0.5f) * 360;
particles.add(p);
}
}
public void createFirework(float x, float y) {
int mainColor = getRandomColor();
// 创建主粒子
Particle main = new Particle();
main.x = x;
main.y = y;
main.vy = -500; // 向上
main.radius = 8;
main.color = mainColor;
main.life = 2.0f;
main.decay = 0.3f;
particles.add(main);
// 主粒子爆炸
new Handler().postDelayed(() -> {
createExplosion(main.x, main.y, 50);
}, 800);
}
public void update(float dt) {
Iterator<Particle> iterator = particles.iterator();
while (iterator.hasNext()) {
Particle p = iterator.next();
p.update(dt);
if (!p.isAlive()) {
iterator.remove();
}
}
}
public void draw(Canvas canvas) {
for (Particle p : particles) {
particlePaint.setColor(p.color);
particlePaint.setAlpha((int) (p.life * 255));
canvas.save();
canvas.translate(p.x, p.y);
canvas.rotate(p.rotation);
// 绘制粒子(可以换成其他形状)
canvas.drawCircle(0, 0, p.radius, particlePaint);
// 绘制拖尾效果
if (p.life > 0.3f) {
particlePaint.setAlpha((int) (p.life * 128));
for (int i = 1; i <= 3; i++) {
canvas.drawCircle(-p.vx * 0.02f * i, -p.vy * 0.02f * i,
p.radius * (1 - i * 0.2f), particlePaint);
}
}
canvas.restore();
}
}
private int getRandomColor() {
int[] colors = {
Color.RED, Color.YELLOW, Color.GREEN,
Color.BLUE, Color.MAGENTA, Color.CYAN
};
return colors[random.nextInt(colors.length)];
}
}
5.1.2 动态波形与流体效果
// 波形效果实现
public class WaveEffectView extends View {
private float[] wavePoints;
private Paint wavePaint = new Paint();
private Path wavePath = new Path();
private float waveSpeed = 0.05f;
private float waveAmplitude = 50;
private float waveFrequency = 0.02f;
private float phase = 0;
public WaveEffectView(Context context) {
super(context);
init();
}
private void init() {
wavePaint.setColor(Color.BLUE);
wavePaint.setStyle(Paint.Style.FILL);
wavePaint.setAntiAlias(true);
wavePoints = new float[getWidth()];
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
wavePoints = new float[w];
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
updateWave();
drawWave(canvas);
// 持续动画
phase += waveSpeed;
invalidate();
}
private void updateWave() {
int width = getWidth();
int height = getHeight();
// 计算波形点
for (int i = 0; i < width; i++) {
// 基础正弦波
float baseWave = (float) Math.sin(i * waveFrequency + phase) * waveAmplitude;
// 添加次级波
float secondaryWave = (float) Math.sin(i * waveFrequency * 2 + phase * 1.7f) *
waveAmplitude * 0.3f;
// 添加噪声
float noise = (float) (Math.random() - 0.5) * 10;
wavePoints[i] = height / 2f + baseWave + secondaryWave + noise;
}
}
private void drawWave(Canvas canvas) {
wavePath.reset();
// 移动到起点
wavePath.moveTo(0, wavePoints[0]);
// 使用二次贝塞尔曲线平滑连接点
for (int i = 1; i < wavePoints.length - 1; i++) {
float x = i;
float y = wavePoints[i];
// 计算控制点
float prevX = i - 1;
float prevY = wavePoints[i - 1];
float nextX = i + 1;
float nextY = wavePoints[i + 1];
float ctrlX1 = (prevX + x) / 2;
float ctrlY1 = (prevY + y) / 2;
float ctrlX2 = (x + nextX) / 2;
float ctrlY2 = (y + nextY) / 2;
wavePath.cubicTo(ctrlX1, ctrlY1, ctrlX2, ctrlY2, x, y);
}
// 闭合路径形成填充区域
wavePath.lineTo(getWidth(), getHeight());
wavePath.lineTo(0, getHeight());
wavePath.close();
// 创建渐变着色器
Shader shader = new LinearGradient(0, getHeight() / 2f, 0, getHeight(),
Color.argb(200, 0, 100, 255),
Color.argb(100, 0, 200, 255),
Shader.TileMode.CLAMP);
wavePaint.setShader(shader);
// 绘制波形
canvas.drawPath(wavePath, wavePaint);
// 绘制波浪线
wavePaint.setShader(null);
wavePaint.setStyle(Paint.Style.STROKE);
wavePaint.setStrokeWidth(3);
wavePaint.setColor(Color.WHITE);
canvas.drawPath(wavePath, wavePaint);
// 恢复填充模式
wavePaint.setStyle(Paint.Style.FILL);
}
}
// 水波纹点击效果
public class RippleView extends View {
private List<Ripple> ripples = new ArrayList<>();
private Paint ripplePaint = new Paint();
class Ripple {
float x, y;
float radius;
float maxRadius;
float alpha;
long startTime;
Ripple(float x, float y, float maxRadius) {
this.x = x;
this.y = y;
this.radius = 0;
this.maxRadius = maxRadius;
this.alpha = 1.0f;
this.startTime = System.currentTimeMillis();
}
void update() {
long currentTime = System.currentTimeMillis();
float elapsed = (currentTime - startTime) / 1000f;
// 半径扩展
radius = Math.min(elapsed * 300, maxRadius);
// 透明度衰减
alpha = 1.0f - (radius / maxRadius);
// 添加阻尼效果
if (radius > maxRadius * 0.7f) {
alpha *= 0.5f;
}
}
boolean isFinished() {
return radius >= maxRadius;
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
ripples.add(new Ripple(event.getX(), event.getY(),
Math.max(getWidth(), getHeight())));
invalidate();
return true;
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
ripplePaint.setStyle(Paint.Style.STROKE);
ripplePaint.setAntiAlias(true);
Iterator<Ripple> iterator = ripples.iterator();
while (iterator.hasNext()) {
Ripple ripple = iterator.next();
ripple.update();
if (ripple.isFinished()) {
iterator.remove();
continue;
}
// 绘制多个同心圆增强效果
for (int i = 0; i < 3; i++) {
float currentRadius = ripple.radius - i * 20;
if (currentRadius <= 0) continue;
float currentAlpha = ripple.alpha * (1 - i * 0.3f);
ripplePaint.setColor(Color.argb(
(int) (currentAlpha * 255), 0, 150, 255));
ripplePaint.setStrokeWidth(3 - i);
canvas.drawCircle(ripple.x, ripple.y, currentRadius, ripplePaint);
}
}
if (!ripples.isEmpty()) {
invalidate();
}
}
}
5.2 文本特效
// 渐变文字特效
public class GradientTextView extends View {
private String text = "Canvas绘图";
private Paint textPaint = new Paint();
private LinearGradient gradient;
private float gradientAngle = 0;
public GradientTextView(Context context) {
super(context);
init();
}
private void init() {
textPaint.setTextSize(100);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
textPaint.setAntiAlias(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updateGradient();
}
private void updateGradient() {
// 根据角度计算渐变方向
float angleRad = (float) Math.toRadians(gradientAngle);
float cos = (float) Math.cos(angleRad);
float sin = (float) Math.sin(angleRad);
float startX = getWidth() / 2f - cos * getWidth() / 2f;
float startY = getHeight() / 2f - sin * getHeight() / 2f;
float endX = getWidth() / 2f + cos * getWidth() / 2f;
float endY = getHeight() / 2f + sin * getHeight() / 2f;
gradient = new LinearGradient(
startX, startY, endX, endY,
new int[]{Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE, Color.MAGENTA},
new float[]{0, 0.25f, 0.5f, 0.75f, 1},
Shader.TileMode.CLAMP
);
textPaint.setShader(gradient);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制描边文字
Paint strokePaint = new Paint(textPaint);
strokePaint.setShader(null);
strokePaint.setStyle(Paint.Style.STROKE);
strokePaint.setStrokeWidth(5);
strokePaint.setColor(Color.BLACK);
// 计算文字居中位置
float textWidth = textPaint.measureText(text);
float x = (getWidth() - textWidth) / 2;
float y = getHeight() / 2f +
(Math.abs(textPaint.ascent()) - textPaint.descent()) / 2;
// 先绘制描边
canvas.drawText(text, x, y, strokePaint);
// 再绘制填充
canvas.drawText(text, x, y, textPaint);
// 添加文字阴影
canvas.save();
canvas.translate(5, 5);
textPaint.setAlpha(100);
canvas.drawText(text, x, y, textPaint);
textPaint.setAlpha(255);
canvas.restore();
// 更新渐变角度
gradientAngle = (gradientAngle + 1) % 360;
updateGradient();
invalidate();
}
}
// 打字机效果
public class TypewriterTextView extends View {
private String fullText = "Hello, Canvas绘图高级技巧!";
private StringBuilder currentText = new StringBuilder();
private Paint textPaint = new Paint();
private Handler handler = new Handler();
private int currentIndex = 0;
private long delay = 100; // 每个字符的延迟
public TypewriterTextView(Context context) {
super(context);
init();
startTyping();
}
private void init() {
textPaint.setTextSize(50);
textPaint.setColor(Color.BLACK);
textPaint.setAntiAlias(true);
}
private void startTyping() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (currentIndex < fullText.length()) {
currentText.append(fullText.charAt(currentIndex));
currentIndex++;
invalidate();
// 随机延迟,模拟真实打字
long nextDelay = delay + (long) (Math.random() * 50);
handler.postDelayed(this, nextDelay);
}
}
}, delay);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制已打出的文字
canvas.drawText(currentText.toString(), 50, 100, textPaint);
// 绘制光标
if (currentIndex < fullText.length()) {
float cursorX = 50 + textPaint.measureText(currentText.toString());
long time = System.currentTimeMillis();
if ((time / 500) % 2 == 0) {
canvas.drawLine(cursorX, 80, cursorX, 120, textPaint);
}
}
}
}
5.3 图像特效
5.3.1 实时滤镜效果
// 实时滤镜处理器
public class ImageFilterProcessor {
// 应用滤镜到Bitmap
public static Bitmap applyFilter(Bitmap source, FilterType filterType) {
Bitmap result = source.copy(source.getConfig(), true);
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
result.getPixels(pixels, 0, width, 0, 0, width, height);
switch (filterType) {
case GRAYSCALE:
applyGrayscale(pixels);
break;
case SEPIA:
applySepia(pixels);
break;
case INVERT:
applyInvert(pixels);
break;
case BRIGHTNESS:
applyBrightness(pixels, 50);
break;
case CONTRAST:
applyContrast(pixels, 1.5f);
break;
case BLUR:
// 使用卷积矩阵模糊
pixels = applyConvolution(pixels, width, height, getBlurKernel());
break;
case SHARPEN:
pixels = applyConvolution(pixels, width, height, getSharpenKernel());
break;
case EDGE_DETECT:
pixels = applyConvolution(pixels, width, height, getEdgeDetectKernel());
break;
}
result.setPixels(pixels, 0, width, 0, 0, width, height);
return result;
}
private static void applyGrayscale(int[] pixels) {
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
int a = Color.alpha(pixel);
int r = Color.red(pixel);
int g = Color.green(pixel);
int b = Color.blue(pixel);
// 灰度公式
int gray = (int) (0.299 * r + 0.587 * g + 0.114 * b);
pixels[i] = Color.argb(a, gray, gray, gray);
}
}
private static void applySepia(int[] pixels) {
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
int a = Color.alpha(pixel);
int r = Color.red(pixel);
int g = Color.green(pixel);
int b = Color.blue(pixel);
// 怀旧色公式
int tr = (int) (0.393 * r + 0.769 * g + 0.189 * b);
int tg = (int) (0.349 * r + 0.686 * g + 0.168 * b);
int tb = (int) (0.272 * r + 0.534 * g + 0.131 * b);
r = Math.min(255, tr);
g = Math.min(255, tg);
b = Math.min(255, tb);
pixels[i] = Color.argb(a, r, g, b);
}
}
private static void applyInvert(int[] pixels) {
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
int a = Color.alpha(pixel);
int r = 255 - Color.red(pixel);
int g = 255 - Color.green(pixel);
int b = 255 - Color.blue(pixel);
pixels[i] = Color.argb(a, r, g, b);
}
}
private static void applyBrightness(int[] pixels, int value) {
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
int a = Color.alpha(pixel);
int r = clamp(Color.red(pixel) + value);
int g = clamp(Color.green(pixel) + value);
int b = clamp(Color.blue(pixel) + value);
pixels[i] = Color.argb(a, r, g, b);
}
}
private static void applyContrast(int[] pixels, float contrast) {
float factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
int a = Color.alpha(pixel);
int r = clamp((int) (factor * (Color.red(pixel) - 128) + 128));
int g = clamp((int) (factor * (Color.green(pixel) - 128) + 128));
int b = clamp((int) (factor * (Color.blue(pixel) - 128) + 128));
pixels[i] = Color.argb(a, r, g, b);
}
}
// 卷积滤波
private static int[] applyConvolution(int[] pixels, int width, int height, float[] kernel) {
int[] result = new int[pixels.length];
int kernelSize = (int) Math.sqrt(kernel.length);
int kernelRadius = kernelSize / 2;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float r = 0, g = 0, b = 0;
// 应用卷积核
for (int ky = -kernelRadius; ky <= kernelRadius; ky++) {
for (int kx = -kernelRadius; kx <= kernelRadius; kx++) {
int pixelX = clamp(x + kx, 0, width - 1);
int pixelY = clamp(y + ky, 0, height - 1);
int pixel = pixels[pixelY * width + pixelX];
float weight = kernel[(ky + kernelRadius) * kernelSize + (kx + kernelRadius)];
r += Color.red(pixel) * weight;
g += Color.green(pixel) * weight;
b += Color.blue(pixel) * weight;
}
}
int a = Color.alpha(pixels[y * width + x]);
result[y * width + x] = Color.argb(a,
clamp((int) r), clamp((int) g), clamp((int) b));
}
}
return result;
}
private static float[] getBlurKernel() {
// 3x3 高斯模糊核
return new float[] {
1/16f, 2/16f, 1/16f,
2/16f, 4/16f, 2/16f,
1/16f, 2/16f, 1/16f
};
}
private static float[] getSharpenKernel() {
// 3x3 锐化核
return new float[] {
0, -1, 0,
-1, 5, -1,
0, -1, 0
};
}
private static float[] getEdgeDetectKernel() {
// Sobel边缘检测核
return new float[] {
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
};
}
private static int clamp(int value) {
return Math.max(0, Math.min(255, value));
}
private static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
enum FilterType {
GRAYSCALE, SEPIA, INVERT, BRIGHTNESS,
CONTRAST, BLUR, SHARPEN, EDGE_DETECT
}
}
六、Canvas动画与交互
6.1 自定义动画实现
// 物理动画系统
public class PhysicsAnimationView extends View {
private ValueAnimator animator;
private float animatedValue = 0;
private Paint circlePaint = new Paint();
private float circleX, circleY;
private float circleRadius = 50;
private float velocityY = 0;
private float gravity = 980; // 像素/秒²
private float damping = 0.8f; // 反弹阻尼
private long lastUpdateTime;
public PhysicsAnimationView(Context context) {
super(context);
init();
startAnimation();
}
private void init() {
circlePaint.setColor(Color.RED);
circlePaint.setStyle(Paint.Style.FILL);
circlePaint.setAntiAlias(true);
circleX = getWidth() / 2f;
circleY = circleRadius;
lastUpdateTime = System.currentTimeMillis();
}
private void startAnimation() {
animator = ValueAnimator.ofFloat(0, 1);
animator.setDuration(Long.MAX_VALUE);
animator.setInterpolator(null); // 不使用插值器,自己控制
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
updatePhysics();
invalidate();
}
});
animator.start();
}
private void updatePhysics() {
long currentTime = System.currentTimeMillis();
float deltaTime = (currentTime - lastUpdateTime) / 1000f; // 转换为秒
lastUpdateTime = currentTime;
// 应用重力
velocityY += gravity * deltaTime;
// 更新位置
circleY += velocityY * deltaTime;
// 边界碰撞检测
if (circleY + circleRadius > getHeight()) {
circleY = getHeight() - circleRadius;
velocityY = -velocityY * damping; // 反弹
// 如果速度太小,停止动画
if (Math.abs(velocityY) < 50) {
velocityY = 0;
circleY = getHeight() - circleRadius;
}
}
// 空气阻力
velocityY *= 0.999f;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
circleX = w / 2f;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制地面
Paint groundPaint = new Paint();
groundPaint.setColor(Color.GRAY);
canvas.drawRect(0, getHeight() - 10, getWidth(), getHeight(), groundPaint);
// 绘制阴影
Paint shadowPaint = new Paint();
shadowPaint.setColor(Color.argb(100, 0, 0, 0));
float shadowScale = 1 - (circleY / getHeight()) * 0.5f;
canvas.drawOval(
circleX - circleRadius * shadowScale,
getHeight() - 10,
circleX + circleRadius * shadowScale,
getHeight() - 5,
shadowPaint
);
// 绘制圆形
canvas.drawCircle(circleX, circleY, circleRadius, circlePaint);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (animator != null) {
animator.cancel();
}
}
}
// 弹簧动画
public class SpringAnimationView extends View {
private SpringAnimation springAnim;
private float springValue = 0;
private Paint paint = new Paint();
public SpringAnimationView(Context context) {
super(context);
init();
setupSpringAnimation();
}
private void init() {
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
}
private void setupSpringAnimation() {
// 创建弹簧动画
springAnim = new SpringAnimation(this, "springValue", 0)
.setSpring(new SpringForce()
.setFinalPosition(1)
.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_MEDIUM))
.addUpdateListener((animation, value, velocity) -> {
springValue = value;
invalidate();
});
// 开始动画
postDelayed(() -> springAnim.start(), 1000);
}
// 必须提供setter方法供SpringAnimation使用
public void setSpringValue(float value) {
this.springValue = value;
}
public float getSpringValue() {
return springValue;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float centerX = getWidth() / 2f;
float centerY = getHeight() / 2f;
// 使用弹簧值控制大小和透明度
float radius = 50 + springValue * 100;
int alpha = (int) (100 + springValue * 155);
paint.setAlpha(alpha);
canvas.drawCircle(centerX, centerY, radius, paint);
// 绘制弹簧轨迹
if (springValue > 0) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(2);
paint.setAlpha(100);
Path path = new Path();
path.moveTo(centerX - radius, centerY);
for (int i = 0; i < 10; i++) {
float x = centerX - radius + i * (radius * 2 / 10);
float y = centerY + (float) Math.sin(i * Math.PI) * 20 * springValue;
path.lineTo(x, y);
}
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.FILL);
}
}
}
6.2 触摸交互处理
// 高级绘图板实现
public class AdvancedDrawingView extends View {
private Paint drawPaint = new Paint();
private Paint previewPaint = new Paint();
private Path currentPath = new Path();
private List<DrawingPath> paths = new ArrayList<>();
private float lastX, lastY;
private boolean isDrawing = false;
private Bitmap drawingBitmap; // 离屏缓冲
private Canvas drawingCanvas;
private int currentColor = Color.BLACK;
private float currentStrokeWidth = 5;
private BlurMaskFilter blurFilter;
class DrawingPath {
Path path;
Paint paint;
DrawingPath(Path path, Paint paint) {
this.path = new Path(path);
this.paint = new Paint(paint);
}
void draw(Canvas canvas) {
canvas.drawPath(path, paint);
}
}
public AdvancedDrawingView(Context context) {
super(context);
init();
}
private void init() {
// 设置绘制画笔
drawPaint.setColor(currentColor);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeWidth(currentStrokeWidth);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setAntiAlias(true);
// 设置预览画笔(半透明)
previewPaint.set(drawPaint);
previewPaint.setAlpha(128);
// 创建模糊滤镜
blurFilter = new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 创建离屏缓冲
drawingBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawingCanvas = new Canvas(drawingBitmap);
// 重绘所有路径到缓冲
redrawAllToBuffer();
}
private void redrawAllToBuffer() {
if (drawingCanvas == null) return;
drawingCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (DrawingPath path : paths) {
path.draw(drawingCanvas);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
startDrawing(x, y);
return true;
case MotionEvent.ACTION_MOVE:
if (isDrawing) {
continueDrawing(x, y, event);
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (isDrawing) {
finishDrawing();
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
// 双指操作:调整笔刷大小
if (event.getPointerCount() == 2) {
adjustBrushSize(event);
}
break;
}
return true;
}
private void startDrawing(float x, float y) {
isDrawing = true;
lastX = x;
lastY = y;
currentPath.reset();
currentPath.moveTo(x, y);
}
private void continueDrawing(float x, float y, MotionEvent event) {
// 使用历史点获得更平滑的曲线
int historySize = event.getHistorySize();
for (int i = 0; i < historySize; i++) {
float historicalX = event.getHistoricalX(i);
float historicalY = event.getHistoricalY(i);
// 使用二次贝塞尔曲线平滑
float midX = (lastX + historicalX) / 2;
float midY = (lastY + historicalY) / 2;
currentPath.quadTo(lastX, lastY, midX, midY);
lastX = historicalX;
lastY = historicalY;
}
// 添加最后一个点
currentPath.lineTo(x, y);
invalidate();
}
private void finishDrawing() {
isDrawing = false;
// 保存当前路径
DrawingPath savedPath = new DrawingPath(currentPath, drawPaint);
paths.add(savedPath);
// 绘制到离屏缓冲
savedPath.draw(drawingCanvas);
currentPath.reset();
invalidate();
}
private void adjustBrushSize(MotionEvent event) {
float distance = getFingerDistance(event);
float previousDistance = getPreviousFingerDistance(event);
if (previousDistance > 0) {
float scale = distance / previousDistance;
currentStrokeWidth *= scale;
currentStrokeWidth = Math.max(1, Math.min(100, currentStrokeWidth));
drawPaint.setStrokeWidth(currentStrokeWidth);
previewPaint.setStrokeWidth(currentStrokeWidth);
invalidate();
}
}
private float getFingerDistance(MotionEvent event) {
float dx = event.getX(0) - event.getX(1);
float dy = event.getY(0) - event.getY(1);
return (float) Math.sqrt(dx * dx + dy * dy);
}
private float getPreviousFingerDistance(MotionEvent event) {
if (event.getHistorySize() > 0) {
float dx = event.getHistoricalX(0, 0) - event.getHistoricalX(1, 0);
float dy = event.getHistoricalY(0, 0) - event.getHistoricalY(1, 0);
return (float) Math.sqrt(dx * dx + dy * dy);
}
return -1;
}
public void setBrushColor(int color) {
currentColor = color;
drawPaint.setColor(color);
previewPaint.setColor(color);
}
public void setBrushMode(BrushMode mode) {
switch (mode) {
case NORMAL:
drawPaint.setMaskFilter(null);
drawPaint.setXfermode(null);
break;
case BLUR:
drawPaint.setMaskFilter(blurFilter);
break;
case ERASER:
drawPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
break;
}
previewPaint.set(drawPaint);
previewPaint.setAlpha(128);
}
public void undo() {
if (!paths.isEmpty()) {
paths.remove(paths.size() - 1);
redrawAllToBuffer();
invalidate();
}
}
public void clear() {
paths.clear();
redrawAllToBuffer();
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 绘制背景
canvas.drawColor(Color.WHITE);
// 绘制网格背景(可选)
drawGridBackground(canvas);
// 绘制已保存的路径(从离屏缓冲)
if (drawingBitmap != null) {
canvas.drawBitmap(drawingBitmap, 0, 0, null);
}
// 绘制当前路径(预览)
if (isDrawing) {
canvas.drawPath(currentPath, previewPaint);
}
// 绘制笔刷预览
drawBrushPreview(canvas);
}
private void drawGridBackground(Canvas canvas) {
Paint gridPaint = new Paint();
gridPaint.setColor(Color.LTGRAY);
gridPaint.setStrokeWidth(1);
gridPaint.setAlpha(50);
int gridSize = 20;
int width = getWidth();
int height = getHeight();
// 绘制垂直线
for (int x = 0; x < width; x += gridSize) {
canvas.drawLine(x, 0, x, height, gridPaint);
}
// 绘制水平线
for (int y = 0; y < height; y += gridSize) {
canvas.drawLine(0, y, width, y, gridPaint);
}
}
private void drawBrushPreview(Canvas canvas) {
float previewX = getWidth() - 100;
float previewY = 100;
Paint previewBgPaint = new Paint();
previewBgPaint.setColor(Color.argb(200, 255, 255, 255));
canvas.drawCircle(previewX, previewY, currentStrokeWidth + 20, previewBgPaint);
canvas.drawCircle(previewX, previewY, currentStrokeWidth, drawPaint);
}
enum BrushMode {
NORMAL, BLUR, ERASER
}
}
七、Canvas与现代Android图形技术结合
7.1 Canvas与OpenGL ES协同
// 在SurfaceView中混合使用Canvas和OpenGL ES
public class HybridSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
private RenderThread renderThread;
private EGLHelper eglHelper;
private boolean isRunning = false;
// Canvas绘制内容
private Bitmap canvasBitmap;
private Paint canvasPaint = new Paint();
public HybridSurfaceView(Context context) {
super(context);
init();
}
private void init() {
holder = getHolder();
holder.addCallback(this);
canvasPaint.setColor(Color.RED);
canvasPaint.setStyle(Paint.Style.FILL);
canvasPaint.setTextSize(50);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 初始化EGL环境
eglHelper = new EGLHelper();
eglHelper.init(holder.getSurface());
// 创建渲染线程
isRunning = true;
renderThread = new RenderThread();
renderThread.start();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 更新Canvas Bitmap大小
if (canvasBitmap != null) {
canvasBitmap.recycle();
}
canvasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
// 通知渲染线程尺寸变化
synchronized (this) {
notify();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
isRunning = false;
try {
renderThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
eglHelper.cleanup();
if (canvasBitmap != null) {
canvasBitmap.recycle();
}
}
private class RenderThread extends Thread {
@Override
public void run() {
while (isRunning) {
renderFrame();
// 控制帧率
try {
Thread.sleep(16); // ~60 FPS
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void renderFrame() {
// 绑定OpenGL ES上下文
eglHelper.makeCurrent();
// 清除颜色缓冲区
GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
// 1. 使用OpenGL ES绘制3D内容
drawOpenGLContent();
// 2. 使用Canvas绘制2D UI
drawCanvasContent();
// 交换缓冲区
eglHelper.swapBuffers();
}
private void drawOpenGLContent() {
// OpenGL ES绘制代码...
// 例如:绘制一个旋转的立方体
// 设置视图和投影矩阵
Matrix.setLookAtM(viewMatrix, 0,
0, 0, -5, // 相机位置
0, 0, 0, // 观察点
0, 1, 0); // 上方向
float ratio = (float) getWidth() / getHeight();
Matrix.frustumM(projectionMatrix, 0,
-ratio, ratio, -1, 1, 3, 7);
// 旋转立方体
long time = System.currentTimeMillis() % 10000;
float angle = (360.0f / 10000.0f) * ((int) time);
Matrix.setRotateM(modelMatrix, 0, angle, 0, 1, 0);
// 组合MVP矩阵
Matrix.multiplyMM(mvpMatrix, 0, viewMatrix, 0, modelMatrix, 0);
Matrix.multiplyMM(mvpMatrix, 0, projectionMatrix, 0, mvpMatrix, 0);
// 绘制立方体...
}
private void drawCanvasContent() {
// 在Canvas Bitmap上绘制
Canvas canvas = new Canvas(canvasBitmap);
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// 绘制2D UI元素
canvas.drawText("FPS: " + calculateFPS(), 50, 100, canvasPaint);
canvas.drawCircle(100, 200, 50, canvasPaint);
// 将Canvas内容作为纹理上传到OpenGL
uploadBitmapAsTexture(canvasBitmap);
// 使用OpenGL绘制这个纹理
drawTextureQuad();
}
private void uploadBitmapAsTexture(Bitmap bitmap) {
int[] textures = new int[1];
GLES20.glGenTextures(1, textures, 0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
}
}
private float calculateFPS() {
// 计算帧率逻辑...
return 60.0f;
}
}
// EGL辅助类
class EGLHelper {
private EGLDisplay eglDisplay;
private EGLContext eglContext;
private EGLSurface eglSurface;
public void init(Surface surface) {
// 初始化EGL显示
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
EGL14.eglInitialize(eglDisplay, version, 0, version, 1);
// 选择配置
int[] configAttribs = {
EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_DEPTH_SIZE, 16,
EGL14.EGL_STENCIL_SIZE, 0,
EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(eglDisplay, configAttribs, 0,
configs, 0, configs.length, numConfigs, 0);
// 创建上下文
int[] contextAttribs = {
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL14.EGL_NONE
};
eglContext = EGL14.eglCreateContext(eglDisplay, configs[0],
EGL14.EGL_NO_CONTEXT, contextAttribs, 0);
// 创建表面
int[] surfaceAttribs = {EGL14.EGL_NONE};
eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, configs[0],
surface, surfaceAttribs, 0);
}
public void makeCurrent() {
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
}
public void swapBuffers() {
EGL14.eglSwapBuffers(eglDisplay, eglSurface);
}
public void cleanup() {
EGL14.eglDestroySurface(eglDisplay, eglSurface);
EGL14.eglDestroyContext(eglDisplay, eglContext);
EGL14.eglTerminate(eglDisplay);
}
}
7.2 Jetpack Compose中的Canvas
// Compose中的Canvas使用
@Composable
fun AdvancedComposeCanvas() {
var rotation by remember { mutableStateOf(0f) }
var scale by remember { mutableStateOf(1f) }
var offset by remember { mutableStateOf(Offset.Zero) }
// 自动旋转动画
LaunchedEffect(Unit) {
while (true) {
rotation += 1f
delay(16) // 60 FPS
}
}
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.White)
.pointerInput(Unit) {
detectTransformGestures(
onGesture = { centroid, pan, gestureScale, gestureRotate ->
scale *= gestureScale
rotation += gestureRotate
offset += pan
}
)
}
) {
Canvas(
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
rotationZ = rotation
scaleX = scale
scaleY = scale
translationX = offset.x
translationY = offset.y
}
) {
val canvasWidth = size.width
val canvasHeight = size.height
// 1. 绘制渐变背景
drawRect(
brush = Brush.verticalGradient(
colors = listOf(Color.Cyan, Color.Blue),
startY = 0f,
endY = canvasHeight
),
size = size
)
// 2. 绘制粒子系统
drawParticles(canvasWidth, canvasHeight)
// 3. 绘制复杂路径
val path = Path().apply {
moveTo(canvasWidth * 0.2f, canvasHeight * 0.5f)
cubicTo(
canvasWidth * 0.4f, canvasHeight * 0.2f,
canvasWidth * 0.6f, canvasHeight * 0.8f,
canvasWidth * 0.8f, canvasHeight * 0.5f
)
}
drawPath(
path = path,
color = Color.Red,
style = Stroke(width = 5f)
)
// 4. 绘制文字沿路径
drawContext.canvas.nativeCanvas.drawTextOnPath(
"Compose Canvas",
path.asAndroidPath(),
0f,
0f,
android.graphics.Paint().apply {
color = android.graphics.Color.WHITE
textSize = 40f
isAntiAlias = true
}
)
// 5. 使用DrawScope的高级功能
drawCircle(
color = Color.Yellow,
radius = 50f,
center = Offset(canvasWidth / 2, canvasHeight / 2),
blendMode = BlendMode.Screen
)
// 6. 绘制阴影效果
drawCircle(
color = Color.Magenta,
radius = 40f,
center = Offset(canvasWidth * 0.3f, canvasHeight * 0.3f),
style = Stroke(width = 10f)
)
// 添加阴影
drawCircle(
color = Color.Black.copy(alpha = 0.3f),
radius = 40f,
center = Offset(canvasWidth * 0.3f + 5f, canvasHeight * 0.3f + 5f),
style = Stroke(width = 10f)
)
}
}
}
@Composable
fun DrawScope.drawParticles(width: Float, height: Float) {
val currentTime = System.currentTimeMillis()
val particleCount = 100
repeat(particleCount) { i ->
// 使用伪随机生成粒子位置
val seed = (i * 1000L + currentTime / 1000) % 10000
val random = Random(seed)
val x = (random.nextFloat() * width)
val y = (random.nextFloat() * height)
// 基于时间变化的粒子大小
val timeFactor = (currentTime % 2000) / 2000f
val size = 10f + 5f * sin(timeFactor * 2 * PI.toFloat() + i).absoluteValue
// 基于位置的粒子颜色
val hue = (x / width + y / height) * 180f
val color = Color.hsv(hue, 0.8f, 0.9f)
drawCircle(
color = color,
radius = size,
center = Offset(x, y),
alpha = 0.7f
)
// 绘制粒子拖尾
drawCircle(
color = color.copy(alpha = 0.3f),
radius = size * 1.5f,
center = Offset(x, y)
)
}
}
7.3 硬件加速最佳实践
// 硬件加速优化示例
public class HardwareAcceleratedView extends View {
private Paint paint = new Paint();
private Bitmap hardwareLayerBitmap;
private boolean useHardwareLayer = true;
public HardwareAcceleratedView(Context context) {
super(context);
init();
}
private void init() {
paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
// 根据API级别选择最佳策略
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Android 5.0+ 使用RenderThread
configureForRenderThread();
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void configureForRenderThread() {
// 启用渲染线程分离
setLayerType(LAYER_TYPE_HARDWARE, null);
// 设置Z轴高度,启用硬件层
setTranslationZ(1);
// 配置Outline以启用圆角硬件加速
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), 20);
}
});
setClipToOutline(true);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
if (useHardwareLayer) {
// 为硬件层创建Bitmap
hardwareLayerBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(hardwareLayerBitmap);
drawToHardwareLayer(canvas);
}
}
private void drawToHardwareLayer(Canvas canvas) {
// 在硬件层上绘制静态内容
canvas.drawColor(Color.WHITE);
// 使用硬件加速友好的绘制操作
Path path = new Path();
path.addCircle(getWidth()/2f, getHeight()/2f, 100, Path.Direction.CW);
Paint layerPaint = new Paint(paint);
layerPaint.setShader(new LinearGradient(
0, 0, getWidth(), getHeight(),
Color.RED, Color.BLUE, Shader.TileMode.CLAMP
));
canvas.drawPath(path, layerPaint);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (useHardwareLayer && hardwareLayerBitmap != null) {
// 绘制硬件层
canvas.drawBitmap(hardwareLayerBitmap, 0, 0, paint);
// 在硬件层上叠加动态内容
drawDynamicContent(canvas);
} else {
// 软件绘制
drawSoftwareContent(canvas);
}
}
private void drawDynamicContent(Canvas canvas) {
// 只绘制动态变化的内容
long currentTime = System.currentTimeMillis();
float angle = (currentTime % 2000) / 2000f * 360;
canvas.save();
canvas.rotate(angle, getWidth()/2f, getHeight()/2f);
canvas.drawRect(getWidth()/2f - 50, getHeight()/2f - 50,
getWidth()/2f + 50, getHeight()/2f + 50, paint);
canvas.restore();
}
private void drawSoftwareContent(Canvas canvas) {
// 完整的软件绘制
canvas.drawColor(Color.WHITE);
// 避免使用硬件加速不支持的操作
// paint.setShadowLayer(10, 0, 0, Color.BLACK); // 这个在硬件加速下可能有问题
// 使用替代方案
drawShadowManually(canvas);
Path path = new Path();
path.addCircle(getWidth()/2f, getHeight()/2f, 100, Path.Direction.CW);
canvas.drawPath(path, paint);
}
private void drawShadowManually(Canvas canvas) {
// 手动实现阴影效果
Paint shadowPaint = new Paint();
shadowPaint.setColor(Color.argb(100, 0, 0, 0));
for (int i = 0; i < 10; i++) {
shadowPaint.setAlpha(100 - i * 10);
canvas.drawCircle(
getWidth()/2f + i * 0.5f,
getHeight()/2f + i * 0.5f,
100 + i,
shadowPaint
);
}
}
// 性能分析辅助方法
public void startPerformanceProfiling() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// 启用绘制时间追踪
setOnDrawListener(new OnDrawListener() {
long lastDrawTime = 0;
int frameCount = 0;
@Override
public void onDraw(View view, long drawTime) {
frameCount++;
if (lastDrawTime == 0) {
lastDrawTime = drawTime;
}
long elapsed = drawTime - lastDrawTime;
if (elapsed >= 1000) { // 每秒钟
float fps = frameCount * 1000f / elapsed;
Log.d("Performance", "FPS: " + fps);
frameCount = 0;
lastDrawTime = drawTime;
}
// 检查绘制时间是否过长
long drawDuration = System.currentTimeMillis() - (drawTime / 1000000);
if (drawDuration > 16) { // 超过16ms可能掉帧
Log.w("Performance", "Long draw: " + drawDuration + "ms");
}
}
});
}
}
// 优化建议
public void applyOptimizations() {
// 1. 避免在onDraw中创建对象
// 2. 使用硬件层缓存静态内容
// 3. 减少过度绘制
// 4. 使用合适的Bitmap配置
// 5. 避免使用硬件加速不支持的操作
setLayerType(useHardwareLayer ?
LAYER_TYPE_HARDWARE : LAYER_TYPE_SOFTWARE, null);
// 启用裁剪以减少过度绘制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
setClipBounds(new Rect(0, 0, getWidth(), getHeight()));
}
// 如果View不透明,可以设置
setWillNotDraw(false);
// 对于静态View,可以设置
// setWillNotCacheDrawing(false);
}
}
八、综合实战案例
8.1 自定义复杂控件实现
仪表盘控件
public class DashboardView extends View {
private Paint arcPaint = new Paint();
private Paint scalePaint = new Paint();
private Paint needlePaint = new Paint();
private Paint textPaint = new Paint();
private Paint valuePaint = new Paint();
private float currentValue = 0;
private float maxValue = 100;
private float minValue = 0;
private int divisions = 10;
private int subdivisions = 5;
private float startAngle = 150;
private float sweepAngle = 240;
private ValueAnimator needleAnimator;
public DashboardView(Context context) {
super(context);
init();
}
private void init() {
// 圆弧画笔
arcPaint.setStyle(Paint.Style.STROKE);
arcPaint.setStrokeWidth(20);
arcPaint.setAntiAlias(true);
arcPaint.setStrokeCap(Paint.Cap.ROUND);
// 刻度画笔
scalePaint.setStyle(Paint.Style.STROKE);
scalePaint.setStrokeWidth(3);
scalePaint.setColor(Color.BLACK);
scalePaint.setAntiAlias(true);
// 指针画笔
needlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
needlePaint.setStrokeWidth(5);
needlePaint.setColor(Color.RED);
needlePaint.setAntiAlias(true);
// 文字画笔
textPaint.setTextSize(30);
textPaint.setColor(Color.BLACK);
textPaint.setTextAlign(Paint.Align.CENTER);
textPaint.setAntiAlias(true);
// 数值画笔
valuePaint.setTextSize(50);
valuePaint.setColor(Color.BLUE);
valuePaint.setTextAlign(Paint.Align.CENTER);
valuePaint.setAntiAlias(true);
valuePaint.setTypeface(Typeface.DEFAULT_BOLD);
}
public void setValue(float value, boolean animate) {
if (animate) {
animateNeedle(value);
} else {
currentValue = value;
invalidate();
}
}
private void animateNeedle(float targetValue) {
if (needleAnimator != null && needleAnimator.isRunning()) {
needleAnimator.cancel();
}
needleAnimator = ValueAnimator.ofFloat(currentValue, targetValue);
needleAnimator.setDuration(1000);
needleAnimator.setInterpolator(new OvershootInterpolator(0.5f));
needleAnimator.addUpdateListener(animation -> {
currentValue = (float) animation.getAnimatedValue();
invalidate();
});
needleAnimator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int size = Math.min(width, height);
int centerX = width / 2;
int centerY = height / 2;
int radius = size / 2 - 40;
// 1. 绘制背景圆弧
drawBackgroundArc(canvas, centerX, centerY, radius);
// 2. 绘制渐变圆弧(当前值)
drawValueArc(canvas, centerX, centerY, radius);
// 3. 绘制刻度
drawScales(canvas, centerX, centerY, radius);
// 4. 绘制刻度文字
drawScaleText(canvas, centerX, centerY, radius);
// 5. 绘制指针
drawNeedle(canvas, centerX, centerY, radius);
// 6. 绘制中心圆
drawCenterCircle(canvas, centerX, centerY);
// 7. 绘制当前数值
drawCurrentValue(canvas, centerX, centerY);
}
private void drawBackgroundArc(Canvas canvas, int centerX, int centerY, int radius) {
RectF arcRect = new RectF(
centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius
);
// 绘制背景圆弧
arcPaint.setColor(Color.LTGRAY);
arcPaint.setAlpha(100);
canvas.drawArc(arcRect, startAngle, sweepAngle, false, arcPaint);
// 绘制圆弧边框
arcPaint.setColor(Color.DKGRAY);
arcPaint.setAlpha(255);
arcPaint.setStrokeWidth(2);
canvas.drawArc(arcRect, startAngle, sweepAngle, false, arcPaint);
arcPaint.setStrokeWidth(20); // 恢复宽度
}
private void drawValueArc(Canvas canvas, int centerX, int centerY, int radius) {
RectF arcRect = new RectF(
centerX - radius,
centerY - radius,
centerX + radius,
centerY + radius
);
// 计算当前角度
float valueRatio = (currentValue - minValue) / (maxValue - minValue);
float currentSweep = sweepAngle * valueRatio;
// 创建渐变着色器
Shader shader = new SweepGradient(centerX, centerY,
new int[]{Color.GREEN, Color.YELLOW, Color.RED},
new float[]{0, 0.5f, 1.0f}
);
// 旋转渐变以匹配起始角度
Matrix matrix = new Matrix();
matrix.postRotate(startAngle - 90, centerX, centerY);
shader.setLocalMatrix(matrix);
arcPaint.setShader(shader);
arcPaint.setStrokeCap(Paint.Cap.ROUND);
canvas.drawArc(arcRect, startAngle, currentSweep, false, arcPaint);
arcPaint.setShader(null); // 清除着色器
}
private void drawScales(Canvas canvas, int centerX, int centerY, int radius) {
int scaleLength = 20;
int majorScaleLength = 30;
float angleStep = sweepAngle / (divisions * subdivisions);
for (int i = 0; i <= divisions * subdivisions; i++) {
float angle = startAngle + i * angleStep;
float radian = (float) Math.toRadians(angle);
boolean isMajorScale = (i % subdivisions == 0);
int length = isMajorScale ? majorScaleLength : scaleLength;
scalePaint.setStrokeWidth(isMajorScale ? 4 : 2);
float startX = centerX + (radius - length) * (float) Math.cos(radian);
float startY = centerY + (radius - length) * (float) Math.sin(radian);
float endX = centerX + radius * (float) Math.cos(radian);
float endY = centerY + radius * (float) Math.sin(radian);
canvas.drawLine(startX, startY, endX, endY, scalePaint);
}
}
private void drawScaleText(Canvas canvas, int centerX, int centerY, int radius) {
int textOffset = 60;
for (int i = 0; i <= divisions; i++) {
float value = minValue + (maxValue - minValue) * i / divisions;
float angle = startAngle + sweepAngle * i / divisions;
float radian = (float) Math.toRadians(angle);
float textX = centerX + (radius + textOffset) * (float) Math.cos(radian);
float textY = centerY + (radius + textOffset) * (float) Math.sin(radian);
// 调整文本位置使其居中
String text = String.valueOf((int) value);
float textWidth = textPaint.measureText(text);
canvas.drawText(text, textX - textWidth / 2, textY + 10, textPaint);
}
}
private void drawNeedle(Canvas canvas, int centerX, int centerY, int radius) {
float valueRatio = (currentValue - minValue) / (maxValue - minValue);
float angle = startAngle + sweepAngle * valueRatio;
float radian = (float) Math.toRadians(angle);
int needleLength = radius - 40;
// 绘制指针
float endX = centerX + needleLength * (float) Math.cos(radian);
float endY = centerY + needleLength * (float) Math.sin(radian);
needlePaint.setColor(Color.RED);
canvas.drawLine(centerX, centerY, endX, endY, needlePaint);
// 绘制指针尾部
float tailLength = 20;
float tailAngle = angle + 180;
float tailRadian = (float) Math.toRadians(tailAngle);
float tailX = centerX + tailLength * (float) Math.cos(tailRadian);
float tailY = centerY + tailLength * (float) Math.sin(tailRadian);
needlePaint.setColor(Color.DKGRAY);
canvas.drawLine(centerX, centerY, tailX, tailY, needlePaint);
// 绘制指针头(三角形)
drawNeedleHead(canvas, endX, endY, angle);
}
private void drawNeedleHead(Canvas canvas, float x, float y, float angle) {
canvas.save();
canvas.translate(x, y);
canvas.rotate(angle);
Path triangle = new Path();
triangle.moveTo(0, 0);
triangle.lineTo(-15, -10);
triangle.lineTo(-15, 10);
triangle.close();
needlePaint.setColor(Color.RED);
canvas.drawPath(triangle, needlePaint);
// 绘制边框
needlePaint.setStyle(Paint.Style.STROKE);
needlePaint.setColor(Color.DKGRAY);
needlePaint.setStrokeWidth(2);
canvas.drawPath(triangle, needlePaint);
needlePaint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.restore();
}
private void drawCenterCircle(Canvas canvas, int centerX, int centerY) {
Paint centerPaint = new Paint();
centerPaint.setStyle(Paint.Style.FILL);
// 创建径向渐变
Shader shader = new RadialGradient(
centerX, centerY, 15,
Color.WHITE, Color.DKGRAY, Shader.TileMode.CLAMP
);
centerPaint.setShader(shader);
canvas.drawCircle(centerX, centerY, 15, centerPaint);
// 绘制边框
centerPaint.setShader(null);
centerPaint.setStyle(Paint.Style.STROKE);
centerPaint.setStrokeWidth(2);
centerPaint.setColor(Color.BLACK);
canvas.drawCircle(centerX, centerY, 15, centerPaint);
}
private void drawCurrentValue(Canvas canvas, int centerX, int centerY) {
String valueText = String.format("%.1f", currentValue);
String unitText = "km/h";
canvas.drawText(valueText, centerX, centerY + 100, valuePaint);
textPaint.setTextSize(20);
canvas.drawText(unitText, centerX, centerY + 130, textPaint);
textPaint.setTextSize(30); // 恢复大小
}
}
九、调试与测试
9.1 Canvas绘图调试技巧
// Canvas调试工具类
public class CanvasDebugHelper {
// 绘制视图边界和padding/margin
public static void drawViewBounds(Canvas canvas, View view) {
Paint boundsPaint = new Paint();
boundsPaint.setStyle(Paint.Style.STROKE);
boundsPaint.setStrokeWidth(2);
// 绘制视图边界(红色)
boundsPaint.setColor(Color.RED);
canvas.drawRect(0, 0, view.getWidth(), view.getHeight(), boundsPaint);
// 绘制padding区域(蓝色)
boundsPaint.setColor(Color.BLUE);
boundsPaint.setAlpha(100);
canvas.drawRect(
view.getPaddingLeft(),
view.getPaddingTop(),
view.getWidth() - view.getPaddingRight(),
view.getHeight() - view.getPaddingBottom(),
boundsPaint
);
// 绘制文本基准线(绿色)
if (view instanceof TextView) {
TextView textView = (TextView) view;
Paint baselinePaint = new Paint();
baselinePaint.setColor(Color.GREEN);
baselinePaint.setStrokeWidth(1);
Layout layout = textView.getLayout();
if (layout != null) {
int lineCount = layout.getLineCount();
for (int i = 0; i < lineCount; i++) {
float baseline = layout.getLineBaseline(i);
canvas.drawLine(0, baseline, view.getWidth(), baseline, baselinePaint);
}
}
}
}
// 绘制绘制时间
public static void drawRenderTime(Canvas canvas, long renderTime) {
Paint timePaint = new Paint();
timePaint.setColor(Color.WHITE);
timePaint.setTextSize(30);
timePaint.setTypeface(Typeface.MONOSPACE);
String timeText = String.format("Draw: %.2fms", renderTime / 1000000.0);
// 背景
Paint bgPaint = new Paint();
bgPaint.setColor(Color.argb(200, 0, 0, 0));
Rect textBounds = new Rect();
timePaint.getTextBounds(timeText, 0, timeText.length(), textBounds);
int padding = 10;
canvas.drawRect(
10 - padding,
10 - padding,
10 + textBounds.width() + padding,
10 + textBounds.height() + padding,
bgPaint
);
// 文字
canvas.drawText(timeText, 10, 10 + textBounds.height(), timePaint);
// 帧率指示器
if (renderTime > 16000000) { // 超过16ms
timePaint.setColor(Color.RED);
} else if (renderTime > 8000000) { // 8-16ms
timePaint.setColor(Color.YELLOW);
} else {
timePaint.setColor(Color.GREEN);
}
canvas.drawCircle(20 + textBounds.width() + padding + 20,
10 + textBounds.height() / 2, 10, timePaint);
}
// 检测过度绘制
public static void highlightOverdraw(Canvas canvas, View view) {
// 这个方法应该在开启过度绘制调试后使用
Paint overdrawPaint = new Paint();
overdrawPaint.setStyle(Paint.Style.FILL);
// 根据过度绘制次数设置不同颜色
// 这需要实际检测,这里只是示例
overdrawPaint.setColor(Color.argb(50, 255, 0, 0)); // 半透明红色
// 绘制全屏半透明层来模拟过度绘制
canvas.drawRect(0, 0, view.getWidth(), view.getHeight(), overdrawPaint);
// 在实际应用中,需要更精确的检测
// 可以考虑使用以下方法:
// 1. 使用getWindowVisibility()检测
// 2. 使用ViewTreeObserver监听绘制
// 3. 使用性能分析工具
}
// 绘制坐标网格
public static void drawCoordinateGrid(Canvas canvas, View view) {
Paint gridPaint = new Paint();
gridPaint.setColor(Color.LTGRAY);
gridPaint.setStrokeWidth(1);
gridPaint.setAlpha(100);
int gridSize = 50; // 网格大小
// 绘制垂直线
for (int x = 0; x < view.getWidth(); x += gridSize) {
canvas.drawLine(x, 0, x, view.getHeight(), gridPaint);
}
// 绘制水平线
for (int y = 0; y < view.getHeight(); y += gridSize) {
canvas.drawLine(0, y, view.getWidth(), y, gridPaint);
}
// 绘制坐标轴
Paint axisPaint = new Paint();
axisPaint.setColor(Color.BLUE);
axisPaint.setStrokeWidth(2);
// X轴
canvas.drawLine(0, view.getHeight() / 2,
view.getWidth(), view.getHeight() / 2, axisPaint);
// Y轴
canvas.drawLine(view.getWidth() / 2, 0,
view.getWidth() / 2, view.getHeight(), axisPaint);
// 绘制坐标标签
Paint labelPaint = new Paint();
labelPaint.setColor(Color.BLACK);
labelPaint.setTextSize(20);
// 原点
canvas.drawText("(0,0)", view.getWidth() / 2 + 10,
view.getHeight() / 2 - 10, labelPaint);
}
}
// 性能监控View
public class PerformanceMonitorView extends View {
private List<Long> drawTimes = new ArrayList<>();
private static final int MAX_SAMPLES = 100;
private Paint paint = new Paint();
private float averageDrawTime = 0;
private float maxDrawTime = 0;
private float fps = 0;
public PerformanceMonitorView(Context context) {
super(context);
init();
}
private void init() {
paint.setColor(Color.WHITE);
paint.setTextSize(30);
paint.setTypeface(Typeface.MONOSPACE);
paint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
long startTime = System.nanoTime();
// 绘制性能图表
drawPerformanceChart(canvas);
long endTime = System.nanoTime();
long drawTime = endTime - startTime;
// 记录绘制时间
drawTimes.add(drawTime);
if (drawTimes.size() > MAX_SAMPLES) {
drawTimes.remove(0);
}
// 计算统计数据
calculateStatistics();
// 绘制统计数据
drawStatistics(canvas);
}
private void drawPerformanceChart(Canvas canvas) {
if (drawTimes.isEmpty()) return;
int width = getWidth();
int height = getHeight();
// 绘制背景
Paint bgPaint = new Paint();
bgPaint.setColor(Color.argb(200, 0, 0, 0));
canvas.drawRect(0, 0, width, height, bgPaint);
// 绘制图表
Paint chartPaint = new Paint();
chartPaint.setStyle(Paint.Style.STROKE);
chartPaint.setStrokeWidth(2);
float maxTime = 0;
for (Long time : drawTimes) {
if (time > maxTime) maxTime = time;
}
float xStep = (float) width / (drawTimes.size() - 1);
float yScale = (float) height / maxTime;
Path chartPath = new Path();
chartPath.moveTo(0, height - drawTimes.get(0) * yScale);
for (int i = 1; i < drawTimes.size(); i++) {
float x = i * xStep;
float y = height - drawTimes.get(i) * yScale;
chartPath.lineTo(x, y);
}
// 根据绘制时间设置颜色
if (maxTime > 16000000) { // 超过16ms
chartPaint.setColor(Color.RED);
} else if (maxTime > 8000000) { // 8-16ms
chartPaint.setColor(Color.YELLOW);
} else {
chartPaint.setColor(Color.GREEN);
}
canvas.drawPath(chartPath, chartPaint);
// 绘制阈值线(16ms = 60fps)
Paint thresholdPaint = new Paint();
thresholdPaint.setColor(Color.WHITE);
thresholdPaint.setStrokeWidth(1);
thresholdPaint.setAlpha(100);
float thresholdY = height - 16000000 * yScale;
canvas.drawLine(0, thresholdY, width, thresholdY, thresholdPaint);
// 绘制阈值标签
canvas.drawText("16ms", width - 100, thresholdY - 10, paint);
}
private void calculateStatistics() {
if (drawTimes.isEmpty()) return;
long total = 0;
maxDrawTime = 0;
for (Long time : drawTimes) {
total += time;
if (time > maxDrawTime) {
maxDrawTime = time;
}
}
averageDrawTime = total / (float) drawTimes.size();
// 计算FPS(基于平均绘制时间)
if (averageDrawTime > 0) {
fps = 1000000000f / averageDrawTime;
}
}
private void drawStatistics(Canvas canvas) {
String stats = String.format(
"Avg: %.2fms\nMax: %.2fms\nFPS: %.1f\nSamples: %d",
averageDrawTime / 1000000f,
maxDrawTime / 1000000f,
fps,
drawTimes.size()
);
// 绘制背景
Paint bgPaint = new Paint();
bgPaint.setColor(Color.argb(200, 0, 0, 0));
Rect textBounds = new Rect();
paint.getTextBounds(stats, 0, stats.length(), textBounds);
int padding = 10;
canvas.drawRect(
10 - padding,
10 - padding,
10 + textBounds.width() + padding,
10 + textBounds.height() + padding,
bgPaint
);
// 绘制统计文本
String[] lines = stats.split("\n");
float y = 10 + paint.getTextSize();
for (String line : lines) {
canvas.drawText(line, 10, y, paint);
y += paint.getTextSize() * 1.2f;
}
}
public void reset() {
drawTimes.clear();
invalidate();
}
}
十、总结与展望
通过本文的深入探讨,我们全面了解了Android Canvas绘图从基础到高级的各个方面。从核心组件到高级技巧,从性能优化到特效实现,Canvas为Android开发者提供了强大的2D图形绘制能力。
关键要点回顾:
- Canvas基础:掌握Canvas、Paint、Path、Bitmap等核心组件的使用是绘图的基础
- 高级技巧:着色器、颜色过滤器、混合模式、矩阵变换等高级功能可以实现复杂视觉效果
- 性能优化:合理使用离屏缓冲、脏矩形更新、硬件加速等技术可以显著提升绘图性能
- 特效实现:粒子系统、波形效果、滤镜处理等特效可以大大增强用户体验
- 现代集成:Canvas与OpenGL ES、Jetpack Compose等现代技术的结合是未来发展方向
实际开发建议:
- 性能优先:始终关注绘图性能,避免过度绘制和不必要的对象创建
- 代码复用:将常用的绘图逻辑封装成可复用的组件
- 测试充分:在不同设备和Android版本上测试绘图效果和性能
- 保持学习:关注Android图形技术的发展,及时学习新的API和最佳实践
未来展望:
随着Android图形技术的不断发展,Canvas也在持续进化。未来我们可以期待:
- 更好的硬件加速支持:更高效的GPU利用和更少的限制
- 更强大的API:更丰富的图形效果和更简单的实现方式
- 与3D图形更好的集成:Canvas与OpenGL ES、Vulkan等3D图形API的无缝结合
- 跨平台支持:Canvas API在跨平台框架中的更好支持
Canvas作为Android 2D图形绘制的核心,虽然已经有十多年的历史,但依然充满活力。掌握Canvas的高级技巧,不仅可以帮助你实现复杂的UI效果,还能为学习更高级的图形技术打下坚实基础。
希望本文能成为你在Android绘图开发中的实用指南,帮助你在实际项目中解决具体问题,创造出令人惊艳的视觉效果。
更多推荐


所有评论(0)