前言
公认的,游戏开发比内容性APP开发要复杂和困难,除了应用状态的保存和还原、数据存储交互、游戏逻辑复杂性等等,更主要的就是视图界面的显示和处理的复杂性——在Android系统提供的控件中,几乎找不到合适的组件,都要自己动手去自定义。其实不管光是游戏开发,平时我们在开发中要实现一个比较美观或炫酷的效果,都是需要自己动手实现的。之前写过几篇关于自定控件的文章——Android自定义控件开发系列,虽然效果不错,但是从学习和提高的角度来看,有点舍本逐末了:在学习中我们的重点是掌握原理,了解机制,而不是看最终效果怎么样;至于学习是否有成果,就是看学完之后能不能根据需要实现心中的效果。所以今天,我就来返回头来从最初的起跑线开始补上之前基本功的缺失——借游戏开发来说说Android View和SurfaceView视图框架(下文“游戏开发”包含自定义控件的意义)。
*注* 本文和解下来的Android 视图框架系列2/3——SurfaceView视图框架、Android 视图框架系列3/3——View和SurfaceView之间的抉择是一个模块,有对比才有特点和区别。
Android游戏开发中常用的3种视图是 View、SurfaceView、GLSurfaceView,它们的继承关系如下:
不过还是建议看Android自定义控件开发系列(零)——基础原理篇中那个唯一的大图!
- View:Android中一个超类,是所有视图类组件的父类,用于显示视图,内置画布,提供图形绘制、触屏回调、按键回调等事件;
- SurfaceView:基于View进行拓展,擅长于不断自主变换外观的控件,适合2D游戏开发;
- GLSurfaceView:基于SurfaceView再次拓展,支持硬件加速,专用于3D游戏开发(在此不予讨论,主要是我也不会)。
View
本篇主要说说View视图框架: View 是 Android 开发中最基础也是最本质的视图基类,在开发中要想实现自定义的控件,大多都是直接继承自View,因为那些可以继承View的子类(甚至是子类的子类)的控件其实都是修修改改,只是接触到自定义控件的皮毛而已。具体还是看Android自定义控件开发系列(零)——基础原理篇吧,不必再重复了。 不管是游戏开发还是内容性 APP 开发,继承 View 主要还是重写 onDraw(Canvas canvas)、onTouchEvent(MotionEvent event)、onKeyDown(int keyCode, KeyEvent event) 方法。看不少游戏开发入门的书,在讲 View 这块时总是在 onKeyDown(int keyCode, KeyEvent event) 上做文章,其实现在这个方法现在也就只用来重写 Home、BACK 键的响应了,什么方向键字母键,手机上根本没有(除非你在电脑上用模拟器),重点还是在绘图和触屏事件的响应。基础的
onDraw(Canvas canvas) 方法入参canvas由 Android 系统框架提供,绘图时直接拿来用就好(这就是所谓的内置画布),有人说有画布为什么不提供 Paint 呢?在 Android 中,绘图 API 都置于 Canvas 类的方法中,系统在提供这个画布时其实做了很多我们看不到的复杂工作(具体我也不了解),并创建一个 Bitmap 内存区域用来保存你画出的内容,而 Paint 只是 drawXxx()各自图形时的一个参数而已,其所能做的也只是设置Paint自身的一些属性,比如 Color、抗锯齿抖动、字体大小……等,可以随时用随时 new 。举个不慎恰当的例子,就像我们这些猿,让我们去哪工作都可以,首先你得给我一个工作平台——相当于画布,我们工作用什么呢?我的能力和心胸中的知识就是我们的画笔,什么时候用、怎么用,到我用的时候再拿出来整理思路去实现——就是设置我们的画笔(神笔马良啊)
从图像到动画的升级

public class MyView extends View implements Runnable{简单解释一下:开子线程要完成绘制内容和位置的计算,计算完后就要通知 onDraw() 方法进行重绘了,但是由于 invalidate() 方法不能在子线程中调用(否则报下图错误),所以我们通过给 UI 线程的 Handler 发消息,让 UI 线程调用 invalidate() 方法。
/**
* 两个构造函数,先不用看
*/
public MyView(Context context) {
this(context, null);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); //根据需要改变的绘制内容
paint = new Paint();
new Thread(this).start(); //运行子线程
}
/**
* 从这里开始看
*/
private int startX = 0, startY = 0; //绘制起点的X、Y坐标
private Bitmap bitmap; //要绘制的内容
private Paint paint;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmap, startY, startY, paint);
}
@Override
public void run() {
int i = 0;
while (i < 100) {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher); //根据需要改变的绘制内容
startX += 10; //根据需要变
startY += 10;
//invalidate(); //报错,错误看截图
handler.sendEmptyMessage(0x001); //给主线程发消息,让主线程来完成invalidate()方法的调用
i++;
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Handler handler = new Handler(){
public void handleMessage(Message msg) {
if(msg.what == 0x001){
invalidate(); //在主线程的Handler调用,通知onDraw()重绘
}
};
};
}
