第三章 显示文字和图片
难度:容易
从本章开始,读者就要编写代码了。按照作者的原则——少一些理论,多一些实践,代码中可能会有跳跃的地方。但是请大家不要着急,随着学习的深入,你很快就会了解其中的奥秘。不过在开始之前,我们还是要先来理顺一下思路,看看完成一个坦克大战游戏需要哪些工作:首先,我们需要一个基本的程序,这个程序能够在Android上运行;这个程序要能够显示图形包括地图,主角和NPC等等;程序能够接受用户的输入,控制主角移动;程序要能够控制NPC和子弹的移动;程序还能对各种事件做出判断,比如击中敌人,获得物品,胜利或者失败。
现在我们就从基本程序开始,一步一步实现它。
首先,让我们看一下刚刚生成的文件目录
在源文件目录下,只有Main.java和R.java两个文件,刚刚被我们命名成Main.java的文件就是程序的入口文件。而R.java是由插件来维护的资源定义文件,我们先不管它。
Main.java内容如下:
package org.yexing.android.games.tank;
import android.app.Activity;
import android.os.Bundle;
public class Main extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
很幸运,Main.java的代码非常之少,而且还有一段注释,以致我们很容易知道函数onCreate的作用,需要解释的只是setContentView()。先不要管注释中提到的Activity和setContentView的参数R.layout.main,我们使用setContentView的另一种形式:setContentView(View view)。setContentView的作用是设定当前使用的视图即View(依此理解,可以有很多个View,需要用哪个就可以把他作为setContentView的参数显示出来)。View是一个非常重要的组件,它可以用来显示文字,图片,也可以接收客户的操作,比如触摸屏,键盘等等,而我们的游戏中正是需要绘图和交互,看来View很符合我们的需要(但是请注意,使用View并不是我们的最终方案,原因会在后面说明。此处介绍View是为了讲解基础的图形和用户控制)。
下面我们就要订制一个属于自己的View,可以通过继承自系统提供的View,并重载相关的函数来实现。创建类的方法如下:
右击包名 New -> Class
我们将这个View类命名为GameView,并且由android.view.View继承
点击Finish,一个View类就创建好了。这里是第一次创建类,以后就不会有图片演示了,请大家记住的这个方法。GameView创建好了,但是代码还有一些错误,这里介绍一下eclipse的使用技巧,将鼠标悬停在有错误的位置,或者将光标停在有错误的行,然后按Ctrl+1键,就会出现修改建议,大部分时候,使用修改建议都可以改正我们的错误,如图
可以看出来,刚刚的错误是因为没有创建构造函数,选择修改建议的第二项,增加一个构造函数
public GameView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
我们的View就创建好了。
回到Main.java,刚刚说了,只要将View作为setContentView的参数,这个View就可以被显示出来:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new GameView(this));
}
现在让我们运行模拟器,看看程序变成什么样子了(启动模拟器的方法见第二章)。
不要意外,屏幕上就是一片空白,因为我们创建了一个View,但是没有让它显示任何内容。下面我们就会在View上显示一段文字和一张图片。
让View显示内容也很简单,只需要重载View的onDraw函数,把相应的语句写入onDraw中即可。打开GameView.java,点击菜单 Source -> Override/Implement Method…
选中onDraw点击OK
下面这段代码就会被加入到程序当中,所有与显示有关的代码都会在这里面完成
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
}
这里我们遇到了又一个非常重要的类Canvas,Canvas一般翻译成画布,所有的绘图操作都是通过Canvas中的函数来完成的,比如显示文字的函数Canvas.drawText(),显示位图的函数Canvas.drawBitmap(),以及各种绘制图形的函数如Canvas.drawRect(),Canvas.drawArc()等等。下面让我们显示一段文字在屏幕上:
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
canvas.drawText("坦克大战", 50, 50, new Paint());
}
坦克大战四个字已经出现在了屏幕上。让我们来详细看一下这条语句:
canvas.drawText("坦克大战", 50, 50, new Paint());
第一个参数是要显示的文字,第二、第三个参数是文字在屏幕上的坐标,说到坐标得多讲两句。在2D编程中,屏幕坐标的原点是屏幕的左上角,横向向右增大,纵向向下增大,如上图所示。最后一个参数是Paint,通常翻译成画笔,它决定了文字或图形的颜色,字体,线条粗细等等,后面用到相应属性的时候会详细介绍。那么这条语句就是在屏幕上(50,50)的位置用缺省的画笔写出“坦克大战”四个字。另外如果eclipse提示代码错误,不要忘了用Ctrl+1。
有了文字,下面就是图像了。显示图像比显示文字略微复杂一些,首先我们要准备一张位图,图片必须是png格式的,文件名只能是小写字母,数字和下划线。
battlecity.png
然后将这张图片copy到工程的res/drawable目录下。可以直接在eclipse的目录树中粘贴。
显示位图的函数是Canvas.drawBitmap(),drawBitmap有很多种形态,我们先看其中最简单的一种
canvas.drawBitmap(bitmap, left, top, paint)
乍一看似乎和drawText差不多,4个参数有三个都相同,但这第一个参数bitmap要比文本复杂得多。首先,他是一个Bitmap类实例,因为我们现在还不需要这个类的其他功能,所以不过多介绍Bitmap,只考虑它是怎么来的。得到Bitmap实例的方法也有很多种,这里只介绍其中的一种
BitmapFactory.decodeResource(res, id);
此方法可以返回一个bitmap实例,但是这个函数还需要两个参数res和id。res是Resources实例,而id是一个整数,下面让我们分别了解这两个参数。res的地位跟bitmap差不多,只需要作为参数被使用,因此,只要得到实例就可以了,获得Resources实例的方法如下:
res = context.getResources();
天哪,事情越来越复杂了,因为这段代码里面有多了一个陌生面孔context。context是Context实例,Context通常翻译做上下文,这个名称似乎有点晦涩,他究竟是什么呢?让我们回头看看写好的程序
public GameView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
这时候我们有一个context实例,继续朔源而上,在Main.java中
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new GameView(this));
}
原来,context指向Main类。好了,我们终于找到res的源头了。还有另外一个分支第二个参数id。
BitmapFactory.decodeResource(res, id);
id是一个整形,它到底是谁的id呢?我们还是得往前面找,还记得我们第一次见到函数setContentView时什么样子么
setContentView(R.layout.main);
对,他的参数是R.layout.main,后来被我们替换成了GameView实例。R.layout.main就是一个整数。它被定义在文件R.java中,我们前面讲过R.java是由插件维护的资源定义文件。说到这里大家应该猜到了吧。让我们打开R.java文件
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int battlecity=0x7f020000;
public static final int icon=0x7f020001;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040001;
public static final int hello=0x7f040000;
}
}
果然,位图文件battlecity.png在这里面也被分配了一个id:R.drawable.battlecity,没错,就是它了,这就是我们要找的id。至此为止,我们终于可以使用drawBitmap了。
对于一次创建,多次使用的资源,我们把他放到构造函数里面。增加了图形显示的GameView如下:
public class GameView extends View {
Bitmap bmp;
public GameView(Context context) {
super(context);
// TODO Auto-generated constructor stub
Resources res = context.getResources();
bmp = BitmapFactory.decodeResource(res, R.drawable.battlecity);
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
canvas.drawText("坦克大战", 0, 50, new Paint());
canvas.drawBitmap(bmp, 0, 100, new Paint());
}
}
运行效果