Canvas类是实现低级API的Displayable类的子类,它提供了绘制方法以及应用程序所忽略的一些低级事件的处理方法,由于Canvas类的paint方法被声明为抽象方法,所以Canvas类也是一个抽象类。应用程序如果要使用,则必须扩展 Canvas类。
在创建Canvas类的子类时,要求程序提供paint方法的实现。低级事件处理方法未声明为抽象方法,其默 认实现为空,也就是说可以什么都不做。因而,应用程序无须提供每个事件的处理方法,应用程序只需要重载其感兴趣的事件处理方法即可。
Canvas类的构造函数声明为protected,可以在子类的构造函数中调用super(),在实际应用 中,一般不用调用。所有需要处理低级事件以及需要在显示屏幕上完成图形绘制的类都必须按照下面的形式重写Canvas类:
class myCanvas extends Canvas
{
…
void paint(Graphics g)
{
}
…
}
显示屏幕上能够实际用于绘制的区域因设备的不同而有所差异,因此具有较好移植性的应用程序一般不定好屏幕的尺 寸,而是通过调用getHeight()和getWidth()方法来获得具体尺寸。例如:
int width = getWidth(); //获取屏幕的宽度属性
int height = getHeight(); //获取屏幕的高度属性
Canvas 的坐标原点位于左上角,其最下角的坐标为(即getWidth()和getHeight()返回的数值),屏幕的上部分为设备保留部分,用来向用户指示电量、信号强度、设备状态等,下面部份用于MIDP实现设置或菜单,既可以是应用程序设置的Command软键,也可以是MIDP实现自动设置的菜单等软键,其位置由系统决定。
如果没有设置为全屏模式,即使没有设置任何软键,画布也无法占用这部分空间,如图3-1所示。
Canvas本身有两种状态,一种是普通默认情况下的状态,一种是全屏状态,可以用 setFullScreenMode()方法来对其设定。
两者之间的区别在于当使用全屏状态时,Title、Ticker以及Command都无法在屏幕上显示,而且 当调用setFullScreenMode()时,不管是什么模式,都会调用sizeChanged()这个方法,并传入屏幕的高度和宽度作为其参数。
可以使用setFullScreenMode(true)方法,然后观察Canvas的绘图区域是否发生改 变。
System.out.println(getHeight()); //输出非全屏高度
setFullScreenMode(true); //将Canvas画布设置为全屏幕属性
System.out.println(getHeight()); //输出全屏高度
编译、运行程序,控制台输出某个模拟器在设置Canvas全屏模式前后的高度分别为177和196。可见调用 setFullScreenMode方法后,屏幕的可绘制区域发生了改变。
调用setFullScreenMode(true)后,将触发sizeChanged事件,此事件从系统接收两个参数,即为Canvas全屏后的width和height,通过这个事件可以获得新的宽、高。如果Canvas当前不可见,那么 sizeChanged会等到showNotify方法调用之前改变当前屏幕的大小参数。
protected void sizeChanged(int w, int h)
{
width = w ;
height = h ;
}
但要注意,setFullScreenMode和sizeChanged并不是同步的,如果调用了 setFullScreenMode(true)之后,立即使用屏幕宽度和高度,仍然有可能是非全屏模式的值。
此外有的设备调用setFullScreenMode(true)后,不会触发sizeChanged,而是 立即返回,因此通过getWidth和getHeight能够马上获得新的屏幕大小参数。
实际上Command的显示与否依赖于设备,有的设备虽然使用了全屏模式,但仍然能看到Command,也就是说全屏模式未必有效。但是如果Command软键无法在屏幕上显示,而且已经为 Command设置相应功能,那么,按下对应的软键仍然会有相应的功能菜单,如图3-2所示。如何在Canvas中使用Command将在后面讨论。
3.1.2 屏幕绘制paint(Graphics g)方法用来绘制屏幕,这个方法是一个抽象方法,应用程序必须实现这个方法才能绘制任何图形,如果没有实现这个方法应用程序将不被编译。paint方法被传入一个Graphics对象g,g所设置的矩形区域定义了屏幕上可绘制的区域,也就是需要重绘的区域。
应用程序不一定要了解paint调用的底层实现,并在这个假定基础上只绘制画布区域中像素的部分。这样做的原 因在于paint方法的特殊性,它可能会被多个repaint方法调用,其中的一些可能来自程序之外,则应用程序只绘制它认为是必需的地方。
注意
在paint方法调用返回后,其中的Graphics对象上的操作是未被定义的,因此应用程序不需 要将这个Graphics对象缓存起来,它只能在这个方法范围之内使用。
Graphics对象有如下属性。
n 绘制目标为显示屏幕,如果使用双缓冲,则其绘制目标是屏幕的后台缓冲。
n 剪裁区域应该包括画布上至少一个像素。
n 当前颜色是黑色。
n 当前字体为默认字体,可以通过Font.getDefaultFont()得到。
n 画笔风格为SOLID。
n 坐标系统的原点位于画布的左上角。
n Canvas是可见的,调用isShow()应该返回true。
例如下面的代码使用Graphics对象将Canvas画布用单色进行填充。
public void paint(Graphics g)
{
g.setColor(0xFFFFFF); //将画笔颜色设置为白色
g.fillRect(0,0,width,height); //将屏幕填充为白色
}
3.1.3 绘制基本操作Graphics类可以提供简单的2D几何绘图功能,它与J2SE中的java.awt.Graphics类 非常相似。Gaphics类提供的唯一一种绘图模式为像素替换,在这种模式下,目标像素将替换为用来绘图的Graphics对象所指定的当前像素值。
1.Graphics对象的获取取得Graphics对象有两个方法,用paint方法传入参数,还可以用Image类的 getGraphics方法。两者区别在于paint方法得到的Graphics对象可以利用相关方法在屏幕上绘图,而利用getGraphics方法得到的Graphics对象主要用于绘制后台缓冲。
2.Graphics的具体作用Graphics类的作用就是在屏幕或者后台缓冲上绘制2D几何图形,这些图形主要包括以下3种。
n 文本:使用当前画笔颜色和设备所能支持的字体绘制指定的字符串或者字符。绘制文本时涉及颜色模型、字体类。
n 图像:可以从图像文件中绘制,也可以从缓存中绘制,图像分成可变图像和不变图像,在绘制过程中涉及到Image类。
n 简单的2D几何图形:这些简单的图形包括直线、矩形、弧、圆角矩形、三角形等。
3.坐标系统画图时在图形相关方法中使用的坐标不代表像素本身,坐标是像素与像素之间空格所构成的坐标。图形的坐标系统 (也就是原点坐标(0,0))定位到绘图空间的左上角,x轴向右延伸,y轴向下延伸,如图3-3所示。
4.复制区域在MIDP 2.0中还支持对整块区域的复制,可以调用copyArea方法来复制大块的区域。
public void copyArea(int x_src,int y_src,int width, int height,int x_dest, int y_dest, int anchor);
这个区域是用矩形(x_src, y_src, width, height)来确定的,(x_src, y_src)代表源区域的起始坐标,width和height分别代表区域的宽和高,区域的锚点放置在绘制目标的(x_dest, y_dest)上。
copyArea方法将源区域中的内容原封不动地复制到目标区域上,即使源区域和目标区域有重叠部分,这种复 制性的绘制也会完成。
3.1.4 绘制直线在Graphics的坐标系统中,直线的表示方法是使用它的两个端点坐标表示L:(x1,y1,x2,y2), 实际上是连接坐标(x1,y1)和(x2,y2) 得到一个坐标的集合,然后连接每个坐标右下角的像素就能够得到这条直线。
例如直线(1,0,5,0)连接坐标(1,0)和(4,0)得到一个坐标集合 (1,0)、(2,0)、(3,0)、(4,0)、(5,0),那么这条直线就是由这4个坐标右下角的像素填充得到。
直线(0,1,0,4)得到坐标集合: (0,1)、(0,2)、(0,3)、(0,4)。这条直线由这4个坐标右下角的4个像素表示。
直线(1,2,2,8)的表示不是特别明显,连接两点得到坐标集合(1,2)、(1,3)、(1,4)和(2,5)、(2,6)、(2,7)、(2,8),这些像素的选取规则为:连接两个端点得到一条直线,取最靠近这条直线的坐标(坐标只能是整数),并且优先选取右边、下边的坐标。
直线(6,2,6,2)起点和终点是一个点,则由这个像素本身来表示。这些直线在坐标中的表示如图3-4所 示。
绘制直线可以调用Graphics对象的drawLine方法,该方法有4个参数,分别输入x,y起 点坐标,x、y终点坐标。例如:
g.drawLine(2,2,5,5); //绘制线条
g.drawLine(1,2,2,8); //绘制线条
注意
如果直线的绘制超出屏幕,则超出部分的像素将被忽略,因此在使用直线时要考虑屏幕的大小限制。
3.1.5 绘制弧形Graphics对象也可以被用来绘制和填充弧形,例如圆和椭圆。弧形的表示方法为 Arc(x,y,w,h,startAngle,arcAngle),它们都由坐标系上的一个轮廓所限定。绘制方法为:
(1)以坐标(x,y)为起点,沿x轴正方向延伸w个 单位,沿y轴的正方向延伸h个单位,得到一个矩形,此虚拟矩形内切绘制一个椭圆(如果w和h相 等,则为圆)。
(2)以矩形的中心为圆心,以时钟3点的方向为0°,逆时针为正方向,从0°正方向旋转startAngle 度,和椭圆相交得到一条直线和一个交点。
(3)从这条直线开始,正方向旋转arcAngle度,得到另一条直线和交点,这样就得到了一个两交点之间的 圆弧。
(4)这个圆弧和两条直线得到一个封闭的区域, 弧线的绘制和填充就以这个封闭区域为基础。整个绘制过程如图3-5所示。
绘制弧线时,根据弧线得到一个与之最接近的坐标集合,然后填充这些坐标左下角的像素。填充弧时,只对在封闭区 域内的像素进行填充。
对于一个360°的弧线,它会覆盖一个宽为w+1,高为h+1的矩形, 而填充弧则覆盖一个宽为w,高为h的矩形,其中w和h都不为0。
如果w和h为0,则填充弧不会影响任何像素,而只要不全为0,绘制弧则 可能绘制一条直线。Graphics提供了两个方法来分别进行弧形的绘制和填充,例如:
g.setColor(0x000000); //设置画笔颜色为黑色
g.drawArc(5,5,120,60,0,360); //绘制一个弧形
g.setColor(0x00FFFF); //设置画笔颜色为淡蓝色
g.fillArc(10,10,150,70,30,80); //填充一个弧形
3.1.6 绘制圆角矩形Graphics也支持绘制和填充圆角矩形,圆 角矩形是矩形和弧的组合。圆角弧形的坐标表示方法为:RoundRect(x,y,w,h,arcWidth,arcHeight),它的矩形部分用 Rect(x,y,w,h)表示,弧形部分用 Arc(x,y,2*arcWidth,2*arcHeight,0,360)表示,然后由0°开始从圆心分割为90°的4个部分,分别和矩形4个角相 切,组合成一个圆角矩形。圆角矩形的绘制原理如图3-6所示。
Graphics提供了两个方法,分别来进行圆角矩形的绘制和填充,例如:
g.setColor(0x000000); //设置画笔颜色为黑色
g.drawRoundRect(30,40,120,120,20,10); //绘制一个圆角矩形
g.setColor(0x00FF00); //设置画笔颜色为绿色
g.fillRoundRect(60,70,30,60,30,30); //填充一个圆角矩形
3.1.7 绘制三角形MIDP 2.0新增了一个方法用来填充一个三角形,该方法原型为:
fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3);
该方法的6个参数分别代表了三角形3个顶点的X、Y轴坐标。例如:
g.setColor(0x000000); //设置画笔颜色为黑色
g.fillTriangle(30, 40, 30, 100, 10, 0); //填充一个三角形
3.1.8 绘制笔触的设置绘制直线、圆弧、矩形和圆角矩形时可以选择使用SOLID或者DOTTED这两种画笔风格。Graphics 对象提供的画笔风格分为两类。
n SOLID:值为0,表示当前画笔为1个像素宽的实线,填充紧靠指定坐标的右下端像素。
n DOTTED:值为1,表示当前画笔为点线,打点频率和长度由MIDP实现决定,直线和弧线的终点不能保证被绘制,矩形的交点也无法保证被绘制。
setStrokeStyle方法用来设置画笔风格,getStrokeStyle方法用来获取当前的画笔风 格,可以使用Graphics对象在任何地方设置画笔风格,设置后会取代前面的设置,影响后续操作,直到再次改变其设置。例如:
g.setStrokeStyle(Graphics.DOTTED); //将笔触风格设置为点线
g.drawLine(10,20,100,20); //绘制线条
g.drawLine(5,50,5,50); //绘制线条
注意
画笔风格不影响填充、文本和图像的绘制操作。
3.1.9 双缓冲技术双缓冲技术是编写J2ME游戏程序的关键技术之一。实际上,双缓冲技术是计算机动画的一项传统技术。造成屏幕 闪烁的主要原因在于,画面在显示的同时程序又在改变它,于是画面闪烁。
解决办法是在内存中开辟一片区域作为后台画面,程序对它更新、修改,完成后再显示它。这样被显示的图像永远是 已经完全画好的图像,程序修改的将不是正在被显示的图像。当然还有其他方法可以解决屏幕闪烁问题,但使用双缓冲技术是一种值得推荐的解决方案。
有些设备本身就支持双缓冲,每次都是先把屏幕重画在缓冲之中,然后再绘制在显示屏幕上,而不是直接绘制在显示 屏幕上。可以使用Canvas类的isDoubleBuffer方法判断设备是否具有双缓冲。
可变图像可以很容易地用作屏幕外缓冲。改写前面绘制不变图像的代码,将所有的绘制都放在可变图像中,然后一次 性地将可变图像绘制到屏幕上去。
package doublebufferdemo;
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import java.io.*;
public class ImageCanvas extends Canvas
{
private Image buffer; //可变图像,作为绘制缓冲
private Image image; //不变图像,用来加载图片文件
public ImageCanvas()
{
try
{
image = Image.createImage("/tree.png"); //加载图片文件
}catch(java.io.IOException e)
{
System.out.println(e.getMessage()); //处理I/O异常
}
buffer = Image.createImage(this.getWidth(), this.getHeight());
//用一个可变图像作为绘制缓冲
Graphics bg = buffer.getGraphics(); //获取缓冲的Graphics对象
bg.setColor(0xFFFFFF);
bg.fillRect(0, 0, getWidth(), getHeight()); //填充整个屏幕
bg.drawImage(image,this.getWidth()/2,this.getHeight()/2, Graphics.VCENTER|Graphics.HCENTER);
}
public void paint(Graphics g)
{
g.drawImage(buffer,0,0,g.TOP|g.LEFT); //将缓冲区上的内容绘制到屏幕上
}
}
编译、运行程序,其结果和前面完全相同,但是却采用了双缓冲技术。对于双缓冲的使用,可以总结出以下几点。
n 定义一个Graphics对象bg和一个Image对象buffer,按屏幕大小建立一个缓冲对象赋给buffer,然后取得buffer的 Graphics对象赋给bg。在这里,Graphics对象可以理解为缓冲的屏幕,Image对象则可当成缓冲屏幕上的图片。
n 在bg(缓冲屏幕)上用drawImage()和drawString()等语句画图,相当于在缓冲屏幕上画图。
n 调用repaint()语句,它的功能是告知系统调用paint()来完成真实屏幕的显示。这里需要注意的是,paint()是一个系统调用语句,不能手 工调用,只能通过paint()语句来调用。
n 在paint(Graphics g)函数里,将buffer(缓冲屏幕上的图片)画到真实屏幕上。
以上的步骤虽然看似繁琐,但是效果还是很不错的。如果想在屏幕上显示什么东西,只要画在bg上,然后调用 repaint()将其显示出来就可以了。