1.使用Graphics2D实现动态效果
(1)invalidate()方法用于重绘组件:
public void invalidate()
public void invalidate(int l, int t, int r, int b)
public void invalidate(Rect dirty)
其中,不带参数表示重绘整个区域,带参数表示重绘指定的区域。简单来讲,View的invalidate()相当于调用了onDraw()方法。invalidate()方法只能在UI线程中调用,如果要在子线程中刷新组件,View 还定义了另一组postInvalidate的方法:
public void postInvalidate()
public void postInvalidate(int left, int top, int right, int bottom)
下面绘制一个左右移动的小球:
public class MyView extends View {
/**
* 小球的水平位置
*/
private int x;
/**
* 小球的垂直位置,固定为 100
*/
private static final int Y = 100;
/**
* 小球的半径
*/
private static final int RADIUS = 30;
/**
* 小球的颜色
*/
private static final int COLOR = Color.RED;
private Paint paint;
/**
* 移动的方向
*/
private boolean direction;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(COLOR);
x = RADIUS;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//根据 x、 y 的坐标值画一个小球
canvas.drawCircle(x, Y, RADIUS, paint);
//改变 x 坐标的值,调用 invalidate()方法后,
//小球将因 x 的值发生改变而产生移动的效果
int width = this.getMeasuredWidth();//获取组件的宽度
if (x <= RADIUS) {
direction = true;
}
if (x >= width - RADIUS) {
direction = false;
}
x = direction ? x + 5 : x - 5;
}
}
在MainActivity中进行重绘操作:
myView = findViewById(R.id.myview);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
myView.postInvalidate();//子线程调用
}
},200,50);
(2)坐标的转换
通过Canvas提供的方法进行坐标的转换主要有四种,平移、旋转、缩放和拉斜。
平移:public void translate(float dx, float dy)
旋转:public void rotate(float degrees) (以当前原点为中心旋转)
public final void rotate(float degrees, float px, float py) (以 px py为中心)
缩放:public void scale(float sx, float sy) (sx sy 分别是X和Y方向的缩放比例)
public final void scale(float sx, float sy, float px, float py) (以sx sy为中心进行缩放)
拉斜:public void skew(float sx, float sy) (sx和sy分别是倾斜角度的tan值)
除了这些变换方法,Canvas还提供了save()和restore()方法,前者是对现场进行保存,后者则是将画面回复到save()调用之前的状态。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.RED);
paint.setStrokeWidth(2);
paint.setStyle(Paint.Style.STROKE);
// 保存现场
canvas.save();
for (int i = 0; i < 10; i++) {
canvas.drawRect(0, 0, 150, 150, paint);
canvas.translate(30, 30);
}
// 恢复现场
canvas.restore();
// 平移坐标,让接下来的图形绘制在上一次图形的下面
canvas.translate(0, 500);
canvas.save();
for (int i = 0; i < 10; i++) {
canvas.drawRect(0, 0, 400, 400, paint);
canvas.scale(0.9f, 0.9f, 200, 200);
}
canvas.restore();
// 平移坐标,让接下来的图形绘制在上一次图形的下面
canvas.translate(0, 500);
canvas.save();
canvas.drawCircle(200, 200, 200, paint);
for (int i = 0; i < 12; i++) {
canvas.drawLine(350, 200, 400, 200, paint);
canvas.rotate(30, 200, 200);//对画布进行旋转操作
}
canvas.restore();
}
另外,Android中定义了一个名为Matrix的类,该类同样可以实现坐标的变换:
移位:public void setTranslate(float dx, float dy)
旋转:public void setRotate(float degrees, float px, float py)
public void setRotate(float degrees)
缩放:public void setScale(float sx, float sy)
public void setScale(float sx, float sy, float px, float py)
拉斜:public void setSkew(float kx, float ky)
public void setSkew(float kx, float ky, float px, float py)
基本使用方法如:
Matrix matrix = new Matrix();
matrix.setTranslate(10, 10);
canvas.setMatrix(matrix);
(3)剪切区
Canvas提供了剪切区的功能,该功能就是对原有的绘制内容进行剪切,从而获取到剪切区的内容。默认提供的方法有:
public boolean clipRect(Rect rect)
public boolean clipRect(RectF rect)
public boolean clipRect(float left, float top, float right, float bottom)
public boolean clipRect(int left, int top, int right, int bottom)
public boolean clipPath(Path path)
同样,对剪切区Canvas支持图形运算,具体的方法有:
public boolean clipRect(RectF rect, Op op)
public boolean clipRect(Rect rect, Op op)
public boolean clipRect(float left, float top, float right, float bottom, Op op)
public boolean clipPath(Path path, Op op)
其中Op运算有6种:
public static enum Op {
DIFFERENCE,
INTERSECT,
REPLACE,
REVERSE_DIFFERENCE,
UNION,
XOR
}
下面根据之前的内容绘制一个指针走动的手表:
1)绘制刻度:
刻度的变化是一长四短,共60根刻度。
2)绘制指针:
分别有秒针、分针、时针,显示的时间是设备的系统时间。
3)定义Timer定时器
定义定时器,每隔一秒钟就绘图区进行一次刷新。
public class MyView extends View {
private Paint paint;
private Calendar calendar;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1);
calendar = Calendar.getInstance();
}
public void run() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
postInvalidate();
}
}, 0, 1000);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 获取组件宽度
int width = this.getMeasuredWidth();
// 获取组件高度
int height = this.getMeasuredHeight();
// 计算圆盘直径,取短的
int len = Math.min(width, height);
// 绘制表盘
drawPlate(canvas, len);
// 绘制指针
drawPoints(canvas, len);
}
private void drawPoints(Canvas canvas, int len) {
// 先获取系统时间
calendar.setTimeInMillis(System.currentTimeMillis());
// 获取时分秒
int hours = calendar.get(Calendar.HOUR) % 12;//转换为 12 小时制
int minutes = calendar.get(Calendar.MINUTE);
int seconds = calendar.get(Calendar.SECOND);
// 画时针
// 角度(顺时针)
int degree = 360 / 12 * hours;
// 转换成弧度
double radians = Math.toRadians(degree);
// 根据当前时计算时针两个点的坐标
// 起点(圆中心),终点:计算得到
int r = len / 2;
int startX = r;
int startY = r;
int endX = (int) (startX + r * 0.5 * Math.cos(radians));
int endY = (int) (startY + r * 0.5 * Math.sin(radians));
canvas.save();
paint.setStrokeWidth(3);
// 0 度从 3 点处开始,时间从 12 点处开始,所以需要将画布旋转 90 度
canvas.rotate(-90, r, r);
// 画时针
canvas.drawLine(startX, startY, endX, endY, paint);
canvas.restore();
// 画分针
// 计算角度
degree = 360 / 60 * minutes;
radians = Math.toRadians(degree);
endX = (int) (startX + r * 0.6 * Math.cos(radians));
endY = (int) (startY + r * 0.6 * Math.sin(radians));
canvas.save();
paint.setStrokeWidth(2);
// 0 度从 3 点处开始,时间从 12 点处开始,所以需要将画布旋转 90 度
canvas.rotate(-90, r, r);
// 画时针
canvas.drawLine(startX, startY, endX, endY, paint);
canvas.restore();
// 画秒针
degree = 360 / 60 * seconds;
radians = Math.toRadians(degree);
endX = (int) (startX + r * 0.8 * Math.cos(radians));
endY = (int) (startY + r * 0.8 * Math.sin(radians));
canvas.save();
paint.setStrokeWidth(1);
// 0 度从 3 点处开始,时间从 12 点处开始,所以需要将画布旋转 90 度
canvas.rotate(-90, r, r);
// 画时针
canvas.drawLine(startX, startY, endX, endY, paint);
// 再给秒针画个“尾巴”
radians = Math.toRadians(degree - 180);
endX = (int) (startX + r * 0.15 * Math.cos(radians));
endY = (int) (startY + r * 0.15 * Math.sin(radians));
canvas.drawLine(startX, startY, endX, endY, paint);
canvas.restore();
}
private void drawPlate(Canvas canvas, int len) {
canvas.save();
int r = len / 2;
canvas.drawCircle(r, r, r, paint);
for (int i = 0; i < 60; i++) {
if (i % 5 == 0) {
paint.setColor(Color.RED);
paint.setStrokeWidth(4);
canvas.drawLine(r + 9 * r / 10, r, len, r, paint);
} else {
//短刻度,长刻度占圆半径的 1/15
paint.setColor(Color.GRAY);
paint.setStrokeWidth(1);
canvas.drawLine(r + 14 * r / 15, r, len, r, paint);
}
canvas.rotate(6, r, r);
}
canvas.restore();
}
}
最后,在MainActivity中调run()方法即可。
显示效果如图所示: