Android中的View就是我们眼睛看到的、屏幕上显示的东东,是Activty的具体内容的体现。每一个View都有一个Canvas(画布),我们可以对它进行扩展,使用画布绘制我们想要的图像。对View进行扩展十分简单,只需要继承View类,重载它的onDraw方法,在onDraw方法中利用画布画出各种图案,包括三角形、点、矩形、线、图片等。View必须在UI线程中刷新屏幕,如:棋牌类游戏。更新画面有两种方式:调用invalidate()或者postInvalidate()方法。
首先比较一下invalidate()和postInvalidate()的区别,两者都是用来实现view的更新,但是前者只能在UI线程中直接调用,后者可以在非UI线程中使用,两者没有参数时都是更新整个屏幕的,可以指定参数如:invalidate(Rect rect) 、invalidate(left, top, right, bottom)、postInvalidate(left, top, right, bottom)更新指定区域。下面通过一个简单的demo来实现在UI线程中和子线程中使用invalidate()更新画布以及使用postInvalidate函数更新画布,在子线程使用invalidate函数,需要借助于Handler来帮忙。这个demo的作用是在用户点击处绘制一个红色的实心圆。
public class GameView extends View {
private int cx;
private int cy;
private Paint p;
public GameView(Context context) {
super(context);
this.cx = 20;
this.cy = 20;
this.p = new Paint();
p.setColor(Color.RED);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawCircle(cx, cy, 10, p);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
//返回false,则该事件消失且接收不到下次事件
return true;
case MotionEvent.ACTION_UP:
int x = (int)event.getX();
int y = (int)event.getY();
changePosition(x, y);
return true;
}
return super.onTouchEvent(event);
}
private void changePosition(int x,int y) {
this.cx = x;
this.cy = y;
this.invalidate();
}
}
上面的代码很简单,就是响应up事件后执行changePosition函数,改变圆圈的坐标并重绘,调用invalidate函数后会重新执行onDraw函数。需要注意的是:监听UP事件时,一定得监听DOWN事件并且DOWN事件一定返回true,否则UP事件不会被监听到。因为如果不监听和处理DOWN事件,super.onTouchEvent(event)会返回false,如果onTouchEvent返回false则表示该事件已消失且不接收下次事件,这样就无法接收到UP事件了。Android中触屏事件和按键事件的分发处理,我们以后再详细讨论。
public class GameView extends View {
//.....
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
int x = (int)event.getX();
int y = (int)event.getY();
GameThread gameThread = new GameThread(x,y);
gameThread.start();
return true;
}
return super.onTouchEvent(event);
}
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
changePosition(msg.arg1, msg.arg2);
}
};
private class GameThread extends Thread {
private int x;
private int y;
public GameThread(int x,int y) {
this.x = x;
this.y = y;
}
public void run() {
Message msg = mHandler.obtainMessage();
msg.arg1 = x;
msg.arg2 = y;
msg.sendToTarget();
}
}
}
其实最终还是在UI线程中执行的invalidate函数,利用了handler来处理线程间的通信,这样有一个好处:就是把一些费事的操作放到子线程中处理,处理完了就通过handler通知ui线程更新画布。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_UP:
int x = (int)event.getX();
int y = (int)event.getY();
GameThread gameThread = new GameThread(x,y);
gameThread.start();
return true;
}
return super.onTouchEvent(event);
}
private void changePosition(int x,int y) {
this.cx = x;
this.cy = y;
}
private class GameThread extends Thread {
private int x;
private int y;
public GameThread(int x,int y) {
this.x = x;
this.y = y;
}
public void run() {
changePosition(x, y);
postInvalidate();
}
}
使用postInvalidate方式跟invalidate+handler的方式原理是一样的,内部也是使用handler机制实现的,不过它更简单使用些,代码量更少。下面简单的跟踪一下sdk的源码实现。
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
从上面的代码可以看到,使用了mHandler去更新UI,mHandler是ViewRootHandler的一个实例,它是在UI线程中创建的。
public Bitmap decodeBitmapFromRes(Context context, int resourseId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Config.ARGB_8888;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(resourseId);
return BitmapFactory.decodeStream(is, null, opt);
}
@Override
protected void onDraw(Canvas canvas) {
Canvas bufferCanvas = new Canvas();
Bitmap bitmap = Bitmap.createBitmap(320, 480, Config.ARGB_8888);
Bitmap img = decodeBitmapFromRes(mContext, R.drawable.sprite);
bufferCanvas.setBitmap(bitmap);
bufferCanvas.drawBitmap(img, 0, 0, null);
canvas.drawBitmap(bitmap, 0, 0, null);
}
个人微信公众号:猿聚于此
个人CSDN博客:任我行吧
个人Github:https://github.com/brooklee145
获取更多文章和相关资源,可关注以上账号。文章欢迎转载,转载请注明出处。