首先,介绍的是Canvas的基本方法
方法签名 | 简要说明 |
---|---|
drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint) | 绘制弧 |
drawBitmap(Bitmap bitmap, Rect src, Rect dst,Paint paint) | 在指定点绘制从源位图中"挖取"的一块 |
drawBitmap(Bitmap bitmap, float left, flost top, Paint paint) | 在指定点绘制位图 |
drawCircle(float cx, float cy, float radius, Paint paint) | 在指定点绘制一个圆形 |
drawLine(float startX, float startY, float stopX, float stopY, Paint paint) | 绘制一条线 |
drawLines(float[] pts, int offset, int count, Paint paint) | 绘制多条线 |
drawOval(RectF oval, Paint paint) | 绘制椭圆 |
drawPath(Path path, Paint paint) | 沿着指定Path绘制任意形状 |
drawPoint(float x, float y, Paint paint) | 绘制一个点 |
drawPoints(float[] pts, int offset, int count, Paint paint) | 绘制多个点 |
drawRect(float left, float top, float right, float bottom, Paint paint) | 绘制矩形 |
drawRoundRect(RectF rect, float rx, float ry, Paint paint) | 绘制圆角矩形 |
drawText(String text, int start ,int end, Paint paint) | 绘制字符串 |
drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) | 沿着路径绘制字符串 |
clipRect(float left, float top, float right, float bottom) | 剪切一个矩形区域 |
clipRegion(Region region) | 剪切指定区域 |
>rotate(float degress, float px, float py): 对Canvas执行旋转变换.
>scale(float sx, float sy, float px, float py): 对Canvas执行缩放变换.
>skew(float sx, float sy): 对Canvas执行倾斜变换.
>translate(float dx, float dy): 移动Canvas.向右移动dx距离(dx为负数即向左移动);向下移动dy距离(dy为负数即向上移动).
Paint代表了Canvas上的画笔,因此Paint类主要用于设置绘制风格,包括画笔颜色.画笔笔触粗细,填充风格等.
Paint提供了如下所示的方法:
方法签名 | 简要说明 |
---|---|
setARGB(int a, int r, int g, int b)/setColor(int color) | 设置颜色 |
setAlpha(int a ) | 设置透明度 |
setAntiAlias(boolean aa) | 设置是否抗锯齿 |
setColor(int color) | 设置颜色 |
setPahtEffect(PathEffect cffect) | 设置绘制路径时的路径效果 |
setShader(Shader shader) | 设置画笔的填充效果 |
setShadowLayer(float radius, float dx, float dy, int color) | 设置阴影 |
setStrokeWidth(float width) | 设置画笔的笔触宽度 |
setStrokeJoin(Paint.Join join) | 设置画笔转弯处的连接风格 |
setStyle(Paint.Style style) | 设置Paint的填充风格 |
setTextAlign(Paint.Align align) | 设置绘制文本时的文字的对齐方式 |
setTextSize(float textSize) | 设置绘制文本时的文字大小 |
下面的MyView重写了onDraw(Canvas)方法,绘制了基本的几何图形.
package com.example.canvastest.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;
public class MyView extends View {
public MyView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context) {
super(context);
}
/**
* 重写该方法,进行绘图
*/
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 把整张画布绘制成白色
canvas.drawColor(Color.WHITE);
// 创建画笔
Paint paint = new Paint();
// 去锯齿
paint.setAntiAlias(true);
// 设置画笔颜色
paint.setColor(Color.BLUE);
// 设置画笔样式
paint.setStyle(Paint.Style.STROKE);
// 设置画笔的宽度
paint.setStrokeWidth(3);
// 绘制圆形
canvas.drawCircle(40, 40, 30, paint);
// 绘制正方形
canvas.drawRect(10, 80, 70, 140, paint);
// 绘制矩形
canvas.drawRect(10, 150, 70, 190, paint);
//
RectF rectF1 = new RectF(10, 200, 70, 230);
// 绘制圆角矩形
canvas.drawRoundRect(rectF1, 15, 15, paint);
//
RectF rectF2 = new RectF(10, 240, 70, 270);
// 绘制椭圆
canvas.drawOval(rectF2, paint);
// 定义一个Path对象,封闭成一个三角形
Path path1 = new Path();
// 设置起点
path1.moveTo(10, 340);
// 线路1
path1.lineTo(70, 340);
// 线路2
path1.lineTo(40, 290);
path1.close();
// 根据Path进行绘制,绘制三角形
canvas.drawPath(path1, paint);
// 定义一个Path对象,绘制一个五角形
Path path2 = new Path();
path2.moveTo(26, 360);
path2.lineTo(54, 360);
path2.lineTo(70, 392);
path2.lineTo(40, 420);
path2.lineTo(10, 392);
path2.close();
// 根据Path进行绘制,绘制三角形
canvas.drawPath(path2, paint);
// -------------设置填充风格后绘制---------------------------
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
// 绘制圆形
canvas.drawCircle(120, 40, 30, paint);
// 绘制正方形
canvas.drawRect(90, 80, 150, 140, paint);
// 绘制矩形
canvas.drawRect(90, 200, 150, 190, paint);
// 绘制圆角矩形
RectF rectF3 = new RectF(90, 200, 150, 230);
canvas.drawRoundRect(rectF3, 15, 15, paint);
// 绘制椭圆
RectF rectF4 = new RectF(90, 240, 150, 270);
canvas.drawOval(rectF4, paint);
// 绘制三角形
Path path3 = new Path();
path3.moveTo(90, 340);
path3.lineTo(150, 340);
path3.lineTo(120, 290);
path3.close();
canvas.drawPath(path3, paint);
// 绘制五角形
Path path4 = new Path();
path4.moveTo(106, 360);
path4.lineTo(134, 360);
path4.lineTo(150, 392);
path4.lineTo(120, 420);
path4.lineTo(90, 392);
path4.close();
canvas.drawPath(path4, paint);
// -------------设置渐变器后绘制------------------
// 为paint设置渐变器
Shader mShader = new LinearGradient(0, 0, 40, 60, new int[] {
Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW }, null,
Shader.TileMode.REPEAT);
paint.setShader(mShader);
//设置阴影
paint.setShadowLayer(45, 10, 10, Color.GRAY);
// 绘制圆形
canvas.drawCircle(200, 40, 30, paint);
// 绘制正方形
canvas.drawRect(170, 80, 230, 140, paint);
// 绘制矩形
canvas.drawRect(170, 150, 230, 190, paint);
// 绘制圆角矩形
RectF rectF5 = new RectF(170, 200, 230, 230);
canvas.drawRoundRect(rectF5, 15, 15, paint);
// 绘制椭圆
RectF rectF6 = new RectF(170, 240, 230, 270);
canvas.drawOval(rectF6, paint);
// 绘制三角形
Path path6 = new Path();
path6.moveTo(170, 340);
path6.lineTo(230, 340);
path6.lineTo(200, 290);
path6.close();
canvas.drawPath(path6, paint);
// 绘制五角形
Path path5 = new Path();
path5.moveTo(186, 360);
path5.lineTo(214, 360);
path5.lineTo(230, 392);
path5.lineTo(200, 420);
path5.lineTo(170, 392);
path5.close();
canvas.drawPath(path5, paint);
//----------设置字符大小后绘制-----------------
paint.setTextSize(24);
paint.setShader(null);
//绘制7个字符串
canvas.drawText("圆形", 240, 50, paint);
canvas.drawText("正方形", 240, 120, paint);
canvas.drawText("矩形", 240, 175, paint);
canvas.drawText("圆角矩形", 240, 220, paint);
canvas.drawText("椭圆形", 240, 260, paint);
canvas.drawText("三角形", 240, 325, paint);
canvas.drawText("五角形", 240, 390, paint);
}
}
Path类: Android提供的Path是一个非常有用的类,它可以预先在View上将N个点连成一条"路径",然后调用Canvas的drawPath(Path path, Paint paint)即可沿着路径绘制图形.实际上Android还为路径绘制提供了PathEffect来定义绘制效果,PathEffect包含了如下子类(每个子类代表一种绘制效果):
Corner PathEffect
Discrete PathEffect
Dash PathEffect
Path Dash PathEffect
Compose PathEffect
Sum PathEffect
这些绘制效果使用语言来表述总显得有些空洞,下面通过一个程序来了解一下绘制效果.接下来的程序绘制7条路径,分别示范了不适用效果和使用上面6种效果的示意.
package com.example.canvastest.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ComposePathEffect;
import android.graphics.CornerPathEffect;
import android.graphics.DashPathEffect;
import android.graphics.DiscretePathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathDashPathEffect;
import android.graphics.PathEffect;
import android.graphics.SumPathEffect;
import android.util.AttributeSet;
import android.view.View;
/**
* 使用Path绘制路径
* @author Administrator
*
*/
public class MyPathView extends View {
private float phase;
private PathEffect[] pathEffects = new PathEffect[7];
private int[] colors;
private Path path;
private Paint paint;
public MyPathView(Context context) {
this(context, null);
}
public MyPathView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyPathView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(4);
// 创建并初始化Path
path = new Path();
path.moveTo(0, 0);
for (int i = 0; i < 15; i++) {
// 生成15个点,随机生成他们的Y坐标,并将它们练成一条Path
path.lineTo(i * 20, (float) Math.random() * 60);
}
// 初始化7个颜色
colors = new int[] { Color.BLACK, Color.BLUE, Color.CYAN, Color.GREEN,
Color.MAGENTA, Color.RED, Color.YELLOW };
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
///将背景填充为白色
canvas.drawColor(Color.WHITE);
//-------下面开始初始化7种路径效果--------
//不使用路径效果
pathEffects[0] = null;
//使用CornerPathEffect路径效果//角路径效应
pathEffects[1] = new CornerPathEffect(10);
//使用DiscretePathEffect路径效果//离散路径效应
pathEffects[2] = new DiscretePathEffect(3.0f, 5.0f);
//DashPathEffect//短跑路径效应
pathEffects[3] = new DashPathEffect(new float[]{20, 10, 5, 10}, phase);
//PathDashPathEffect//短跑的路径路径效应
Path p = new Path();
p.addRect(0, 0, 8, 8, Path.Direction.CCW);
pathEffects[4] = new PathDashPathEffect(p, 12, phase, PathDashPathEffect.Style.ROTATE);
//ComposePathEffect//组成的路径效应
pathEffects[5] = new ComposePathEffect(pathEffects[2], pathEffects[4]);
//SumPathEffect//总和路径效应
pathEffects[6] = new SumPathEffect(pathEffects[4], pathEffects[3]);
//将画布移动到(8,8)出绘制
canvas.translate(8, 8);
//依次使用7种不同路径效果,7种颜色来绘制路径
for (int i = 0; i < pathEffects.length; i++) {
paint.setPathEffect(pathEffects[i]);
paint.setColor(colors[i]);
canvas.drawPath(path, paint);
canvas.translate(0, 60);
}
//改变phase值,形成动画效果
phase += 1;
invalidate();
}
}
除此之外,Android的Canvas还提供了一个DrawTextOnPath(String text,Path path, float hOffset, float vOffset, Paint paint)方法,该方法可以沿着Path绘制文本,其中hOffset参数指定水平偏移,vOffset参数指定垂直偏移.
package com.example.canvastest.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
/**
* 使用drawTextOnPaht,沿着Path(路径)绘制文本
*
* @author Administrator
*/
public class MyPathTextView extends View {
final String DRAW_STR = "绘制文本";
private Path[] paths = new Path[3];
Paint paint;
public MyPathTextView(Context context) {
this(context, null);
}
public MyPathTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyPathTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paths[0] = new Path();
paths[0].moveTo(0, 0);
for (int i = 0; i < 7; i++) {
// 生成7个点,随机生成它们的y坐标,并将它们连成一条path
paths[0].lineTo(1 * 30, (float) Math.random() * 30);
}
paths[1] = new Path();
RectF rectf = new RectF(0, 0, 200, 120);
paths[1].addOval(rectf, Path.Direction.CCW);
paths[2] = new Path();
paths[2].addArc(rectf, 60, 180);
// 初始化画笔
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.CYAN);
paint.setStrokeWidth(1);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.WHITE);
canvas.translate(40, 40);
// 设置从右边开始绘制(右对齐)
paint.setTextAlign(Paint.Align.RIGHT);
paint.setTextSize(20);
// 绘制路径
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(paths[0], paint);
// 沿着路径绘制一段文本
paint.setStyle(Paint.Style.FILL);
canvas.drawTextOnPath(DRAW_STR, paths[0], -8, 20, paint);
// 对canvas进行坐标变换:画布下移60
canvas.translate(0, 60);
// 绘制路径
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(paths[1], paint);
// 沿着路径绘制一段文本
paint.setStyle(Paint.Style.FILL);
canvas.drawTextOnPath(DRAW_STR, paths[1], -20, 20, paint);
// 对canvas进行坐标变换:画布下移120
canvas.translate(0, 120);
// 绘制路径
paint.setStyle(Paint.Style.STROKE);
canvas.drawPath(paths[2], paint);
// 沿着路径绘制一段文本
paint.setStyle(Paint.Style.FILL);
canvas.drawTextOnPath(DRAW_STR, paths[2], -10, 20, paint);
}
}
下面是一个使用双缓冲机制实现的画面板例子
package com.example.canvastest.view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class DrawView extends View {
float preX;
float preY;
private Paint paint = null;
final int VIEW_WIDTH = 320;
final int VIEW_HEIGHT = 480;
// 定义一个内存中的图片,该图片将作为缓冲区
Bitmap cacheBitmap = null;
// 定义cachebitmap上的canvas对象
Canvas cacheCanvas = null;
Path path = null;
public DrawView(Context context) {
this(context, null);
}
public DrawView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DrawView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 设置画笔的颜色
paint = new Paint(Paint.DITHER_FLAG);
paint.setColor(Color.RED);
// 设置画笔的风格
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1);
// 抗锯齿
paint.setAntiAlias(true);
paint.setDither(true);
// 创建一个与该View相同大小的缓冲区
cacheBitmap = Bitmap.createBitmap(VIEW_WIDTH, VIEW_HEIGHT,
Bitmap.Config.ARGB_8888);
// 设置cacheCanvas将会绘制到内存中的cacheBitmap上
cacheCanvas = new Canvas();
path = new Path();
cacheCanvas.setBitmap(cacheBitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
preX = x;
preY = x;
break;
case MotionEvent.ACTION_MOVE:
path.quadTo(preX, preY, x, y);
preX = x;
preY = y;
break;
case MotionEvent.ACTION_UP:
cacheCanvas.drawPath(path, paint);
path.reset();
break;
}
invalidate();
// 返回true表明处理方法已经处理该事件
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
// 将该bitmap绘制到该View组件上
canvas.drawBitmap(cacheBitmap, 0, 0, paint);
// 沿着path绘制
canvas.drawPath(path, paint);
}
}
一个弹球游戏例子
package com.example.canvastest;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.Window;
import android.view.WindowManager;
public class PinBallActivity extends Activity {
/** 桌面的宽度 */
private int tableWidth;
/** 桌面的高度 */
private int tableHeight;
/** 随机数生成器 **/
private Random random = new Random();
/** 球拍的垂直位置 */
private int racketY;
/** 球拍的水平位置 */
private int racketX = random.nextInt(200);
/** 球拍的宽度 */
private final int RACKET_WIDTH = 70;
/** 球拍的高度 */
private final int RACKET_HEIGHT = 20;
/** 小球的大小 */
private int BALL_SIZE = 12;
/** 小球的X坐标 */
private int ballX = random.nextInt(200) + 20;
/** 小球的Y坐标 */
private int ballY = random.nextInt(10) + 20;
/**返回一个-0.5~0.5的比率,用于控制小球的运行方向*/
private double xyRate = random.nextDouble()- 0.5;
/**小球的纵向运行速度*/
private int ySpeed = 10;
/**小球的横向运行速度*/
private int xSpeed = (int) (ySpeed * xyRate * 2);
/*** 游戏是否结束的游标 */
private boolean isLose = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 去掉窗口标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 全屏显示
getWindow().setFlags(WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT);
// 创建FameView组件
final GameView gameView = new GameView(this);
setContentView(gameView);
// 获取窗口管理器
WindowManager manager = getWindowManager();
// 获取密度
Display display = manager.getDefaultDisplay();
DisplayMetrics displayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
// 获得屏幕的宽和高
tableWidth = displayMetrics.widthPixels;
tableHeight = displayMetrics.heightPixels;
// 初始化球拍的垂直位置
racketY = tableHeight - 80;
final Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 0x123) {
gameView.invalidate();
}
};
};
gameView.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
// 获取由那个键触发的事件
switch (event.getAction()) {
// 控制挡板左移
case KeyEvent.KEYCODE_A:
if (racketX > 0)
racketX -= 10;
break;
// 控制挡板右移
case KeyEvent.KEYCODE_D:
if (racketX < tableWidth - tableWidth)
racketX += 10;
break;
}
// 通知GameView重绘
gameView.invalidate();
return true;
}
});
final Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
//如果小球碰到左边边框
if(ballX <= 0 || ballX >= tableWidth - BALL_SIZE){
xSpeed = -xSpeed;
}
//如果小球高度超出了球拍的位置,且横向不在球拍的范围之内,游戏结束
if(ballY >= racketY - BALL_SIZE && (ballX < racketX || ballX > racketX + RACKET_WIDTH)){
timer.cancel();
//设置游戏是否结束的旗标为true
isLose = true;
}
//如果小球位于球拍之内,且到达球拍位置,小球反弹
else if(ballY <= 0 || (ballY >= racketY - BALL_SIZE && ballX >racketX && ballX <= racketX + RACKET_WIDTH)){
ySpeed = -ySpeed;
}
//小球的坐标增加
ballX += xSpeed;
ballY += ySpeed;
//发送消息,通知系统重绘组件
handler.sendEmptyMessage(0x123);
}
}, 0, 100);
}
class GameView extends View {
Paint paint = new Paint();
public GameView(Context context) {
this(context, null);
}
public GameView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public GameView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 接收焦点
setFocusable(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 设置样式
paint.setStyle(Paint.Style.FILL);
// 抗锯齿
paint.setAntiAlias(true);
// 如果游戏已经结束
if (isLose) {
// 颜色
paint.setColor(Color.RED);
;
// 字体大小
paint.setTextSize(40);
canvas.drawText("游戏结束", 50, 200, paint);
}
// 如果游戏还没有结束
else {
// 设置颜色,绘制小球
paint.setColor(Color.rgb(240, 240, 80));
canvas.drawCircle(ballX, ballY, BALL_SIZE, paint);
// 设置颜色,绘制球拍
paint.setColor(Color.rgb(80, 80, 200));
canvas.drawRect(racketX, racketY, racketX + RACKET_WIDTH,
racketY + RACKET_HEIGHT, paint);
}
}
}
}