`
guoxinzz
  • 浏览: 431378 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

J2ME简明教程 中

 
阅读更多

第五章 MIDP低级界面开发——使用LCDUI低级API

高级API使用简单、有很高的可移植性,却无法控制许多细节。要对界面更多的进行控制,必须使用低级API。

5.1 Canvas类开发简介

低级界面屏幕都继承自名为Canvas的屏幕类。Canvas类提供了一系列键盘低级事件和绘图接口,具体的绘图操作则由一个名为Graphics的图形类来完成。

5.1.1 Canvas类简介

Canvas即画布,可以在其上绘制不同图案。Canvas提供了一个绘图接口方法paint(Graphics g),凡是继承Canvas的继承类都必须实现paint()方法,因此可以在paint方法中实现屏幕的绘画代码。

示例:

class MyCanvas extends Canvas

{

public void paint(Graphics g)

{

}

}

paint方法传入了Grpahics类型的参数g,具体的绘画将由Graphics的g实现。可以看出Canvas类和Graphics类的关系是画布与画笔的关系。

5.1.2 低级API与低级事件

Canvas可以处理低级事件,但并非处理所有的系统事件。设备支持哪些系统事件,必须由硬件的支持程度来判断。Canvas提供一些方法判断硬件支持程度。

功能

检测方法

低级事件/回调函数

键盘事件

支持

keyPressed(int keycode)

keyReleased(int keycode)

屏幕事件

支持

showNotity()

hideNotify()

重绘事件

支持

paint(Graphics g)

是否支持双缓冲

Canvas.isDoubleBuffered()

是否支持repeat

Canvas.hasRepeatdEvent()

keyRepeated(int keycode)

是否支持触控屏幕事件

Canvas.hasPointerEvents()

pointerPressed(int x, int y)

pointerReleased(int x, int y)

是否支持触控屏幕拖拽事件

Canvas.hasPointerMotionEvnets()

pointerDragged(int x, int y)

机器一定会支持的键盘事件有keyPressed()、keyReleased(),屏幕事件showNotify()、hideNotidy(),以及重绘事件paint()。

注意:除了showNotidy()之外,其它回调函数只有在此Canvas是目前屏幕上的画面时才会被调用。

5.1.3 重绘事件

示例:

//CanvasTestMidlet.java

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

public class CanvasTestMidlet extends MIDlet

{

private Display display;

public CanvasTestMidlet()

{

display = Display.getDisplay(this);

}

public void startApp()

{

MyCanvas mc = new MyCanvas();

display.setCurrent(mc);

}

public void pauseApp()

{

}

public void destroyApp(boolean unconditional)

{

}

}

//MyCanvas.java

import javax.microedition.lcdui.*;

public class MyCanvas extends Canvas

{

/** Creates a new instance of MyCanvas */

public MyCanvas()

{

}

public void paint(Graphics g)

{

}

}

任何时候都可以自行调用repaint()产生重绘事件。repaint()有两个同名方法,其中一个需要四个参数,用来指定重画区域的X、Y坐标,宽度与高度;另外一个无参数,代表重绘整个屏幕。调用repaint()之后会立刻返回,继续下面工作,调用paint()回调函数的工作则由一个专门处理UI的线程来完成。若希望等到paint()完成后再返回,可以在repaint()之后立刻调用serviceRepaints()方法。

注意:serviceRepaints()用来强制队列中的重绘事件快点做完,如果队列中没有重绘事件,则serviceRepaints()什么也不会做,因此在调用serviceRepaints()之前,通常伴随一个repaint()。

5.1.4 坐标系统

在使用绘图函式前,请先注意MIDP 中X 坐标与Y 坐标的定义方式。传统的笛卡尔坐标其原点在左下角,向右X 坐标值递增,向上Y坐标值递增。

但是我们在手机的屏幕上做图时,变成向右X 坐标值递增,向下Y 坐标值递增。

5.1.5 像素(Pixel)

我们在所有图形相关函数之中所使用的坐标所代表的并非像素本身,而是指像素和像素之间的空格所构成的坐标,如下图所示:

像素与像素之间所构成的坐标

所以一般我们所说的坐标(3,1)并非指位于(3,1)这个像素,而是指像素(2,0)、(2,1)、(3,0)、(3,1)所包围的这个部分。也正因为坐标指的并非图素本身,所以会造成在绘制图型和填满区块时有所差异,这两者的不同我们将在以后说明。

5.1.6 Graphics入门

paint(Graphics g)方法会传入一个Graphics对象作为参数,可以把该对象当作是一个抽象的画笔,调用Graphics的方法,就可以在这个画布上绘图。

编写我们自己的Canvas时,要做的第一件事就是把画面清空,然后才开始绘图,避免画面上残留前一个画面所遗留下的东西。

//清屏

public void paint(Graphics g)

{

g.setColor(255, 0, 255);

g.fillRect(0, 0, getWidth(), getHeight());

… …

}

上述范例中,我们使用Graphics的setColor()来设置画笔颜色:

setColor(int r, int g, int b)注意,rgb的值限定在0~255之间。

或setColor(int rgb) 直接传入0x00RRGGBB这样的整数。

设定好颜色后,可以使用getRedComponent()、getGreenComponent()、getBlueComponent()分别取得R、G、B的颜色设定。或者直接使用getColor()取得0x00RRGGBB这样的整数,也就是说,最后第0~7 位代表蓝色、8~15 代表蓝色,16~23 代表红色。

getDisplayColor()较特殊,返回机器上绘图时真正使用的颜色。有些机器不具有显示所有颜色的能力。屏幕的灰度数可以用getGrayScale()取得,也可以用setGrayScale(),灰度数取值在0~255之间。使用这个函式的时候请特别注意,如果您已经使用了相对应的setGrayScale()来设定灰阶色阶数,那么getGrayScale()函式只是单纯地传回设定值。但是如果您没有设定灰阶色阶数,那么呼叫getGrayScale()函式的时候,会导致系统利用目前作用色的R、G、B 值来做大量运算,并求得最接近的灰阶色阶数。

5.1.7 绘制直线

我们可以使用Graphics 类别的drawLine()函式绘制线段。DrawLine 的四个参数分别是起点X 坐标,起点Y 坐标、终点X 坐标、终点Y 坐标。举例来说,如果我们函式呼叫为:g.drawLine(1,1,1,6)

则实际绘制的线段如下图所示:

实际绘制出来的线段的位置

我们可以发现坐标右边的相素都会被填满。

如果我们函式调用为:

g.drawLine(1,1,6,1)

则实际绘制的线段如下图所示:

实际绘制出来的线段的位置

我们可以发现坐标下方的像素都会被填满。

当我们绘图形时,有所谓的笔触(stroke style)。Graphics提供两种笔触,分别是Graphics.SOLID和Graphics.DOTTED:

g.setStrokeStyle(Graphics.DOTTED);

相应的取得目前所用笔触:g.getStrockStyle()

5.1.8 画弧形

我们可以使用Graphics 类的drawArc()方法绘制弧形。drawArc 共有6 个参数,它们分别是:前四个决定弧形所在的矩形范围,第五个决定起始角度,第六个参数则决定弧形本身所涵盖的角度。

如果我们方法调用为:

g.drawArc(20,10,width,height,45,90);

则实际绘制的弧形如下图所示:

实际绘制出来的弧形

填充弧形

我们可以使用Graphics 类别的fillArc()函式填充弧形。fillArc 共有6 个参数,它们分别是:前四个决定弧形所在的矩形范围,第五个决定起始角度,第六个参数则决定弧形本身所涵盖的角度。

如果我们方法调用为:

g.fillArc(20,15,width,height,45,90);

则实际绘制的填充弧形如下图所示:

实际绘制出来的填充弧形

5.1.9 矩形

画矩形:我们可以使用Graphics 类别的drawRect()函式绘制矩形。drawRect 有4 个参数,分别是起点X 坐标、起点Y 坐标、宽度、长度。

如果我们函数调用为:

g.drawRect(1,1,6,8)

则实际绘制的矩形如下图所示:

实际绘制出来的矩形

我们可以发现所构成的矩形路径,其右边和下方的像素都被填满了。

画圆角矩形:我们可以使用Graphics 类别的drawRoundRect()函式绘制圆角矩形。其实drawRoundRect()和drawRect()函式的前四个参数意义相同,唯一的差距只有在最后两个参数,它们分别是圆角所在矩形的宽度,以及圆角所在矩形的高度。

如果我们方法调用为:

g.drawRoundRect(1,1,6,8,arcWidth,arcHeight)

则实际绘制的圆角矩形,在矩形的部分和使用drawRect()的结果相同,差别只有在四个直角的样子不再是直角,而变成圆角。

如下图所示:

实际绘制出来的圆角矩形

填充矩形:我们可以使用Graphics 类别的fillRect()函式填充矩形。fillRect 有4 个参数,分别是起点X 坐标、起点Y 坐标、宽度、长度。

如果我们方法调用为:

g.fillRect(1,1,6,8)

则实际绘制的矩形如下图所示:

实际绘制出来的填充矩形

我们可以发现只有包含在矩形路经之内的图素才会被填满,这和drawRect()函式的结果有所不同(上下都差一个图素的大小)。

填充圆角矩形:我们可以使用Graphics 类别的fillRoundRect()函式填充圆角矩形。其实fillRoundRect()和fillRect()函式的前四个参数意义相同,唯一的差距只有在最后两个参数,它们分别是圆角所在举行的宽度,以及圆角所在矩形的高度。

如果我们方法调用为:

g.fillRoundRect(1,1,6,8,arcWidth,arcHeight)

则实际绘制的圆角矩形,在矩形的部分和使用fillRect()的结果相同,差别只有在四个角的样子不再是直角,而变成圆角,如下图所示:

实际绘制出来的填充圆角矩形

5.1.10 三角形

绘制三角形,使用三个顶点,分别画线即可,因此MIDP只提供填充三角形的功能:

g.fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) ;

练习:尝试自己绘制三角形,并填充它

5.2 Canvas与屏幕事件处理

Canvas本身具有两种状态:

z 普通状态

z 全屏状态

可以使用setFullScreenMode()设定Canvas状态。

示例:

/*

* FullScreenCanvas.java

*

* Created on 2006年2月26日, 上午5:54

*/

import javax.microedition.lcdui.*;

/**

*

* @author Allan

*/

public class FullScreenCanvas extends Canvas implements CommandListener

{

public FullScreenCanvas()

{

setTitle("Full screen test");

setTicker(new Ticker("running..."));

addCommand(new Command("full screen", Command.SCREEN, 1));

addCommand(new Command("normal", Command.SCREEN, 1));

setCommandListener(this);

}

public void paint(Graphics g)

{

g.setColor(255, 255, 255);

g.fillRect(0, 0, getWidth(), getHeight());

}

public void commandAction(Command c, Displayable s)

{

String cmd = c.getLabel();

if (cmd.equals("full screen"))

{

setFullScreenMode(true);

}

else if (cmd.equals("normal"))

{

setFullScreenMode(false);

}

}

public void sizeChanged(int w, int h)

{

System.out.println("width:" + w);

System.out.println("height:" + h);

}

public void hideNotify()

{

System.out.println("应用程序区域被覆盖");

}

public void showNotify()

{

System.out.println("屏幕显示");

}

}

几个重要观念:

1) 对于Canvas低级API,标题、Ticker、Command区域依然有用;

2) 全屏模式下,标题、Ticker、Command区域无法显示,但原本对应到按钮的地方仍然存在,只是看不见;

3) 调用setFullScreenMode()时,不管设置成全屏模式还是正常模式,sizeChanged()都会被调用,并传入当前屏幕的高度和宽度。

4) 当屏幕被系统画面(如菜单、来电显示等)覆盖时,会自动调用hideNotify(),告知应用程序目前的画面被覆盖了。当这些系统画面消失时,系统将调用showNotify()告知应用程序。Canvas第一次显示在屏幕上时,系统也会调用showNotify()。Canvas移出屏幕(有其它displayable被显示,调用setCurrent())时,hideNotify()会被调用。

5) 当我们使用Graphics绘图时,零坐标会随着模式的改变而改变,所以零点不是绝对的,而是相对于应用程序区而言。因为屏幕的大小会改变,所以在清除屏幕(fillRect())的时候使用canvas.getHeight()和canvas.getWidth()取得屏幕大小,而不是固定值。

5.3 键盘事件处理

当Canvas子类正作用于屏幕时,按下任何按钮,就会引发keyPressed()方法,并传入一个代表该按钮的整数,而放开按钮之后,会引发keyReleased()方法,并传入一个代表该按钮的整数值。系统如果传入小于0的值,则为不合法keycode。

某些机器上还可以支持连发事件(即一直按着按钮持续一段时间),该事件会引发keyRepeated()

方法,并传入一个代表该按钮的数值,但该事件并非所有机器都支持,所以我们有必要使用Canvas类中的hasRepeatEvents()方法询问系统是否支持连发事件。

示例:

import javax.microedition.lcdui.*;

public class KeyEventTestCanvas extends Canvas

{

private boolean pressed = false;

public KeyEventTestCanvas()

{

}

public void paint(Graphics g)

{

g.setColor(125, 125, 125);

g.fillRect(0, 0, getWidth(), getHeight());

if (pressed)

{

g.setColor(0, 0, 0);

g.drawLine(20, 20, 120, 20);

g.drawLine(20, 20, 20, 100);

g.setColor(255, 255, 255);

g.drawLine(120, 20, 120, 100);

g.drawLine(20, 100, 120, 100);

}

else

{

g.setColor(255, 255, 255);

g.drawLine(20, 20, 120, 20);

g.drawLine(20, 20, 20, 100);

g.setColor(0, 0, 0);

g.drawLine(120, 20, 120, 100);

g.drawLine(20, 100, 120, 100);

}

}

protected void keyPressed(int keycode)

{

pressed = true;

repaint();

}

protected void keyReleased(int keycode)

{

pressed = false;

repaint();

}

}

此例中,把

protected void keyPressed(int keycode)

{

pressed = true;

repaint();

}

protected void keyReleased(int keycode)

{

pressed = false;

repaint();

}

改为

protected void keyPressed(int keycode)

{

repaint();

pressed = true;

}

protected void keyReleased(int keycode)

{

repaint();

pressed = false;

}

结果是一样的,这是因为回调函数都是在同一个UI的线程中执行,因此理论上,repaint()只是产生一个重绘事件就立刻返回,系统必须等到keyPressed()/keyReleased()执行完毕之后才能继续调用paint()重绘屏幕。

5.4 键盘响应

Canvas里定义了几个常数

KEY_NUM0、KEY_NUM1 ~ KEYNUM9

KEY_STAR、KEY_POUND共12个

可以利用这几个常数判定事件处理方法所传进来的keyCode,得知那个按钮被按下。

为了程序可以跨平台执行,建议使用这些标准的定义键。

玩游戏的时候通常2、4、6、8分别代表上、下、左、右,星字键代表发射,井字代表跳跃。但并非所有机器上都会有相同的键盘排列方式,也并非一定按照此方式设定。为了设计Game的时候方便,MIDP规范中,Canvas中定义了几个与Game键盘代码相关的常数,分别是UP、DOWN、LEFT、RIGHT、FIRE、GAME_A、GAME_B、GAME_C、GAME_D。这些定义与之前的定义有所重复,但是因为有了一些抽象性,在移植的时候也就方便多了。

常用方法:

1. getGameAction(int keyCode);

该方法传入keyCode,会返回所代表的Game键盘代码。

switch(getGameAction(keyCode))

{

case Canvas.FIRE:

fire();

break;

}

2. getKeyCode()

该方法传入Game键盘码,返回所代表的keyCode。

if (keyCode == getKeyCode(Canvas.LEFT))

{

moveLeft();

}

3. 可以利用Canvas的getKeyName()取得该keyCode所代表的按键名称

示例:

import javax.microedition.lcdui.*;

/**

*

* @author Allan

*/

public class KeyEventTestCanvas extends Canvas

{

private boolean pressed = false;

//cross坐标

private int x;

private int y;

private final int length = 20;

private int dxy = 5;

/** Creates a new instance of KeyEventTestCanvas */

public KeyEventTestCanvas()

{

x = getWidth()/2;

y = getHeight()/2;

if (this.hasRepeatEvents())

{

System.out.println("支持连发");

}

else

{

System.out.println("不支持连发");

}

}

public void paint(Graphics g)

{

g.setColor(128, 128, 128);

g.fillRect(0, 0, getWidth(), getHeight());

paintButton(g, 10, 10, 120, 90, pressed);

paintCross(g, x, y, length);

}

public void paintButton(Graphics g, int x, int y, int w, int h, boolean pressed)

{

if (pressed)

{

g.setColor(0, 0, 0);

g.drawLine(x, y, x+w, y);

g.drawLine(x, y, x, y+h);

g.setColor(255, 255, 255);

g.drawLine(x+w, y, x+w, y+h);

g.drawLine(x, y+h, x+w, y+h);

}

else

{

g.setColor(255, 255, 255);

g.drawLine(x, y, x+w, y);

g.drawLine(x, y, x, y+h);

g.setColor(0, 0, 0);

g.drawLine(x+w, y, x+w, y+h);

g.drawLine(x, y+h, x+w, y+h);

}

}

/*

*@parameter x 十字中心点x坐标

*@parameter y 十字中心点y坐标

*/

public void paintCross(Graphics g, int x, int y, int length)

{

g.setColor(255, 0, 0);

g.drawLine(x-length, y, x+length, y);

g.drawLine(x, y-length, x, y+length);

}

public void keyPressed(int keycode)

{

//打印keycode代表的按键名称

//System.out.println(getKeyName(keycode));

int action = getGameAction(keycode);

switch (action)

{

case Canvas.UP:

y -= dxy;

break;

case Canvas.DOWN:

y += dxy;

break;

case Canvas.LEFT:

x -= dxy;

break;

case Canvas.RIGHT:

x += dxy;

break;

case Canvas.FIRE:

pressed = true;

break;

}

repaint();

}

public void keyReleased(int keycode)

{

int action = getGameAction(keycode);

switch (action)

{

case Canvas.FIRE:

pressed = false;

break;

}

repaint();

}

public void keyRepeated(int keycode)

{

int action = getGameAction(keycode);

switch (action)

{

case Canvas.UP:

y -= dxy;

break;

case Canvas.DOWN:

y += dxy;

break;

case Canvas.LEFT:

x -= dxy;

break;

case Canvas.RIGHT:

x += dxy;

break;

case Canvas.FIRE:

pressed = true;

break;

}

repaint();

}

}

5.5 触控屏幕的事件处理

当Canvas是当前画面,数控笔在屏幕上点击,就会引发pointerPressed()方法,并传入其

当前处于屏幕的x与y坐标,放开出控笔后,会引发pointerReleased()方法,并传入当前屏幕位置的x与y坐标。某些机器上可以产生拖拽事件(即一直按住屏幕拖拽),该事件会引发pointerDragged()方法,并传入当时处于屏幕位置的x与y坐标。

并非所有设备都支持触控事件,我们可以使用Canva类中的hasPointerEvents()方法获得系统是否支持触控笔事件的信息。

并非所有设备都支持拖拽事件, 我们可以使用Canvas类中的hasPointerMotionEvents()方法判断系统是否支持触控笔拖拽事件。

示例:

public class PointerEventTestCanvas extends Canvas implements CommandListener

{

private int x1;

private int y1;

private int x2;

private int y2;

private Command backCommand;

/** Creates a new instance of PointerEventTestCanvas */

public PointerEventTestCanvas()

{

backCommand = new Command("返回", Command.BACK, 1);

addCommand(backCommand);

setCommandListener(this);

if (hasPointerEvents())

{

System.out.println("支持数控笔");

}

else

{

System.out.println("不支持数控笔");

}

if (hasPointerMotionEvents())

{

System.out.println("支持数控笔拖拽");

}

else

{

System.out.println("不支持数控笔事件");

}

}

public void paint(Graphics g)

{

g.setColor(255, 255, 0);

g.fillRect(0, 0, getWidth(), getHeight());

g.setColor(0, 0, 0);

g.drawLine(x1, y1, x2, y2);

}

public void pointerPressed(int x, int y)

{

x1 = x;

y1 = y;

}

public void pointerReleased(int x, int y)

{

x2 = x;

y2 = y;

repaint();

}

public void pointerDragged(int x, int y)

{

x2 = x;

y2 = y;

repaint();

}

public void commandAction(Command c, Displayable s)

{

if (c == backCommand)

{

}

}

}

5.6低级事件和高级事件同时出现

当高级事件和低级事件同时出现时,系统会自动判断。如果按钮属于系统的,就会交给高级

事件处理方法来处理,如果不是,才会由低级事件来做。

5.7 绘制字符串

Graphics类提供了绘制字符串的方法,原形如下:

public void drawString(String str, int x, int y, int anchor)

参数:

x、y:相对于屏幕原点的x、y坐标

anchor:定位点

注意:即使x、y相同,anchor不同,具体位置还是不同的(后面详述)

另外,需要了解的是字符串本身显示要占用屏幕上的一个矩形的空间。anchor的作用就是设置字符串所处矩形区域位于屏幕坐标的哪个位置。

5.8 Image类

Image 类是我们在处理图形时常常会用的类别,如果根据它的产生方式, 我们可以细分成可修改( mutable ) 和不可修改(immutable)两种。要辨别一个Image 对象是可修改还是不可修改,您可以呼叫Image 对象的isMutable()方法得知。我们可以使用getWidth()与getHeight()取得该Image 对象的宽度与高度。

要产生不可修改的Image 对象,主要方法有三种:

1. 从影像文件读取:根据MIDP 的规定,所实现MIDP 的厂商至少要提供读取PNG(Portable Network Graphics)影像文件的功能。有些厂商会支持其它如GIF 影像文件的功能,但是不建议使用,因为很可能让您的MIDlet 无法跨平台。

2. 由Byte 数组建立:我们可以经由从网络或resourcebundle 里的文字文件读入一连串的byte 数组,然后用此数组产生不可修改的Image 对象。

3. 从其它Image 对象(可修改或不可修改皆可)来产生。

范例:

import javax.microedition.lcdui.*;

public class ImageCanvas extends Canvas

{

private Image img;

/** Creates a new instance of ImageCanvas */

public ImageCanvas()

{

try

{

img = Image.createImage("/mario.PNG");

}

catch (Exception e)

{

e.printStackTrace();

}

}

public void paint(Graphics g)

{

g.drawImage(img, 0, 0, g.TOP|g.LEFT);

}

}

在此范例之中,我们使用:img = Image.createImage("/mario.PNG");

从MIDlet Suite 之中读取名为mario.PNG的图片。

然后利用g.drawImage(image, 0, 0, g.TOP|g.LEFT);画出此Image对象。第二种建立不可修改Image 对象的方法是利用其方法:

createImage(byte[] imagedata, int imageOffset, int imageLength)

第三种方法则是利用方法: createImage(Image source)就可以从现有可修改或不可修改的Image 对象取得一份不可修改的拷贝。

可修改的Image 对象

建立一个可修改的Image 对象非常简单,只要呼叫Image 对象的静态方法:

createImage(int width,int height)

即可建立一个可修改的Image 对象。事实上,可修改的Image和Double Buffering 的技术息息相关,可修改的Image 对象实际上就是一个供人在背景绘图的off screen。因此在建立可修改的Image 对象前,您应该先呼叫Canvas 类别的isDoubleBuffered()函式来确定您的机器是否支持Double Buffering 技术,如果该函式传回false,那么您就不该试图建立可修改的Image 对象。

一旦您取得了可修改的Image 对象,我们就可以呼叫Image 类别的getGraphics()取得代表off screen 的Graphics 对象,在off screen 绘图并不会影响到正常的画面(on screen)。

最后,我们可以利用on screen ( 由paint 函式传入的Graphics 物件) 的drawImage()函

式绘出可修改Image 对象的内容。

示例:

Image source;

source = Image.createImage(“... ”) ;

//建立可修改Image对象

Image copy = Image.createImage(source.getWidth(), source.getHeight());

Graphics g = copy.getGraphics();//获取copy的Graphics对象

g.drawImage(source, 0, 0, Graphics.TOP|Graphics.LEFT);

5.9 绘制图片、文字以及定位点的作用

绘制图片、字符串或是单一文字都会用到定位点(Anchor)的概念。定位点代表的意义是,绘制图形跟文字时,所指定的X、Y坐标指的是何种意义。

定位点定义共有七种:

Graphics.TOP

Graphics.BOTTOM

Graphics.LEFT

Graphics.RIGHT

Graphics.HCENTER

Graphics.VCENTER

Graphics.BASELINE它们对文字和图形的控制都具有意义。

注意:图中标有VCENTER参数,主要是因为MIDP1.0提供了该参数。但MIDP2.0中已经不允许使用该参数,主要是因为这个参数不好计算而且实际使用的意义也不大,如果在MIDP2.0中调用该参数,将会抛出异常。

这几种定义可以有意义地组合。举例来说,如果我们选择使用TOP 加上LEFT,则绘制文字时,我们会使用函式:

g.drawString(“文字xyzh”, 0, 0, Graphics.TOP|Graphics|LEFT) ;

绘制图形时,我们会使用函式:

g.drawImage(image, 0, 0, g.TOP|g.LEFT);

这时画面上的结果为:

不管是drawString()或是drawImage()其第二与第三个参数所指定的坐标指的是定位点所参考的起始地址。以上述结果为例,我们指定(0,0)为定位点参考起始位置,然后又选择的TOP 与LEFT 作为定位点,代表(0,0)这个坐标为字符串或图形绘制在屏幕上时左上角的点。

再举个例子,如果我们选择使用BOTTOM 加上HCENTER,则绘制文字时,我们会使用函式:

g.drawString(“文字xyzh”, 0, 0,Graphics.BOTTOM|Graphics.HCENTER) ;

绘制图形时,我们会使用函式:

g.drawImage(image, 0, 0, g.BOTTOM |g.HCENTER);

这时画面上的结果为:

由此我们可以归纳出,如果您使用的方法为:

g.drawString("Hello",x,y,g.TOP|g.LEFT) ;

g.drawString("Hello",x,y,0) ;

跟我们使用

g.drawString("Hello",x + stringWidth()/2,y + getHeight(),g.BOTTOM|g.HCENTER) ;

两者的意义是相同。

思考练习:居中字符串。

5.10 字体

当我们需要在屏幕上汇出文字时,我们常常需要一些有关字体的相关数据,这时就需要Font 类别的辅助。通常,我们会使用Font.getDefaultFont()取得代表系统预设所使用字型的Font 对象。或者也可以自行使用Font.getFont()来取得代表特定字型的对象。

getFont() 共有三个参数,

Font f = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE) ;

他们分别是外观( face )、样式(style)、以及尺寸(size)。他们分别有各种选项:

外观: Graphics.FACE_MONOSPACE 定宽字体

Graphics.FACE_PROPORTIONAL 比例字体

Graphics.FACE_SYSTEM 系统字体

样式 : Graphics.STYLE_BOLD 加粗字体

Graphics.STYLE_ITALIC 倾斜字体

raphics.STYLE_PLAIN 常规字体

Graphics.STYLE_UNDERLINED 下划线

尺寸 : Graphics.SIZE_LARGE 大号字体

Graphics.SIZE_MEDIUM 中号字体

Graphics.SIZE_SMALL 小号字体

需要注意的是,底层不一定全部支持。getFont()还有另一个只有一个参数的重载方法,只有PONT_INPUT_TEXT和DON’T_STATIC_TEXT两种可以选择,这个方法用来取得系统用来显示输入用的字体以及一般常用的字体。

但是最实际程序之中,我们最常使用的是Graphics 类别的getFont()方法取得当时显示在画面上的屏幕所使用的字型。同理,我们也可以使用Graphics 类别的setFont()方法设定所使用的字体:

g.setFont(f);

当我们取得代表字型的对象之后,我们就可以利用getFace()函式取得其外观、getStyle()取得其样式、getSize()取得其尺寸。而样式的部分我们也可以改用isBold()、isItalic()、isPlain()、isUnderlined()函式来取得相关信息,这是因为样式是可以合成的,一个字型的样式很可能同时是黑体与斜体的组合。

Font 类别除了可以帮我们取得字型的外观、样式与尺寸之外,还能帮助我们取得该字型在屏幕上的相关属性,请参考下图:

Font 的属性

其中:

charWidth()取得屏幕上某个字符使用该字体显示时的宽度;

charsWidth()计算一串字符显示时的宽度;

stringWidth()取得屏幕上某个字符串使用该字体显示时的宽度;

substringWidth()则是某个字符串的子字符串显示时的宽度;

getBashLinePosition()可以让我们知道从字体最顶点到baseline的距离;

getHeight()可以取得最顶点到最低点的距离。

5.11 颜色

Grpahics类提供了3种颜色设置方法:

public void setColor(int r, int g, int b)

public void setColor(int RGB)

public void setGrayScale(int value)

其中,setGrayScale作用是绘制灰阶,只是0到255共256种;setColor方法的作用是设置RGB颜色模式以在屏幕上显示彩色的颜色。

RGB模型:对于彩色图像中的每个RGB(Red、Green、Blue)分量,为每个像素指定一个0(黑色)到255(白色)之间的强度值。当三个分量相等时,结果是中性灰色;当所有分量的值均为255时,结果为白色;当这些值都为0时,结果为纯黑色。

setColor(int RGB)的参数格式是0x00RRGGBB,高2位0x00被忽略,仅仅计算RRGGBB,高2位主要用来计算色彩的Aplpha混合的。

5.12 调整坐标原点

Graphics类提供了调整屏幕原点位置的方法法:

public void translate(int x, int y)

改变原点的坐标在手机屏幕上的位置,并把相对于原来坐标系统原点的坐标(x,y)作为新的原点。需要注意的是,每次调用translate方法,都是针对当前的坐标系统原点进行调整的,并不是以屏幕左上角进行调整的。

例如:当前坐标为(0,0),如果调用了translate(2,3)则当前原点坐标为原来屏幕的(2,3)坐标,如果再调用translate(4,5),则坐标(4,5)是相对于坐标(2,3)的,所以相对于最原始的坐标系统中的坐标(6,8)。

一定要注意每次转移原点都是根据当前屏幕的原点坐标为标准的。

Grphics还提供了2个计算目前坐标与原始状态下原点坐标的距离的方法。它们是: getTranslateX()和getTranslateY()。

因为一个坐标系统可以进行多次坐标原点的转移,如果希望原点恢复到原始状态,只要再转移一个负变量就是原点往坐标轴的负方向移动,可以使用如下代码恢复原始状态的圆点位置:

g.translate(-getTranslateX(), -getTranslateY()) ;

如果希望在已经转移了原点的环境中使用绝对位置(ax,ay),绝对位置就是指相对于原始状态的原点位置:

g.translate(ax – getTranslateX(), ay – getTranslateY());

5.14 裁剪区

在处理图像时,经常会碰到图片比较大,而屏幕只能显示图像一部分的情况。因此需要确定图像中哪些部分位于显示区域内,而哪些内容落在显示区域之外,以便只显示落在显示区域内的那部分图像。这个选择的过程成为裁剪。

裁剪区的作用:只有在裁剪区域内的绘图过程才会真正有效,在区域外的无效,即使在区域外之行了绘图方法也是不会显示的。

Graphics类提供了简单的裁剪方法,原形如下:

public void setClip(int x, int y, int width, int height)

setClip可以设置需要裁剪的开始坐标(x,y),然后指定矩形的裁剪区域的宽和高。当屏幕重绘的时候,可以保证屏幕的其它部分的内容不必重绘,仅仅重绘需要更新的部分内容。一般使用了裁剪方法以后,应该恢复到原来的裁减区域。

例:

int oldClipX = g.getClipX();

int oldClipY = g.getClipY();

int oldClipWidth = g.getClipWidth();

int oldClipHeight = g.getClipHeight();

//设置裁减区域

g.setClip( 50, 50, 100, 100);

//恢复原来的裁减区域

g.setClip(oldClipX, oldClipY, oldClipWidth, oldClipHeight);

5.15 重绘机制

当需要重绘屏幕的时候,可以调用Canvas类的repaint()方法,然后程序会自动调用paint()方法完成重绘,如果仅仅需要屏幕的一部分更新,可以使用repaint方法的一个重载方法,指定需要更新区域的坐标以及宽度和高度,请求部分屏幕重绘, 例如:

repaint(10, 10, 50, 50);

重绘起始坐标为(10, 10),宽度为50, 高度为50的屏幕区域。

5.16 双缓存技术

双缓存技术是计算机动画的一项传统技术。

屏幕闪烁的主要原因在于:一幅画面正在显示的时候,程序在改变它,然后在一幅图片还没有完全显示完毕又请求重新绘制,于是表现为画面闪烁。

解决闪烁的办法:在内存中操作需要处理的图片,程序对内存中的图片更新、修改、完成后再显示它。这样显示出来的图片永远是完全画好的图像,程序修改的将不是正在被显示

的图像。

5.17 动画制作

基本的动画,是将显示在屏幕上的角色等的动作与描绘的位置作连续的变化,产生动态的效果。

第六章 流程控制的设计模式
在LCDUI的架构下,画面上同时只能显示一个Displayable的子类实例。造成MIDP程序设计中,最令人头痛的地方莫过于程序的流程控制,也就是画面之间的切换。本章针对程序的流程控制,提出一种设计模式。让程序的流程控制能够更方便、更简单、更具可维护性。
一、 系统分析与设计
用户启动MIDlet——〉启动画面Splash
|
|
V
“版权” “开始” “说明”
版权声明主画面 游戏画面辅助说明Help
| “返回”按钮 “返回”
|
| “设置”
设置画面辅助说明
| “返回”
|
| “离开”
|
V
分析画面特性:
1) 辅助说明:
每个辅助说明外观上大致相同,只有说明文字不同而已。用Form来实现。
2) 版权说明:
这类型的画面只是要给用户一些相关的说明信息。可以用Alert来实现。画面在整个系统中最好只有一个,除了可以保持本身状态,还可以降低内存的使用。
3) 主画面
方便用户操作,可以用IMPLICIT类型的List比较恰当。此画面在整个系统中最好只有一个,除了可以保持本身状态外,还可以降低内存的使用。
4) 设定画面
方便用户操作,用Form比较恰当。此画面在整个系统中最好只有一个。降低内存的使用。
5) 游戏画面
用Canvas来实现。此画面在系统中最好只有一个,保持本身状态,降低内存使用。
6) 启动画面
指在程序刚启动时用到,用Alert实现。
二、 流程控制器
将整个系统的流程控制交给一个统一的流程控制器来完成,流程控制器Navigator来实现。
由于系统的每个地方都需要用到MIDlet实例和Display实例,为避免重复编写程序,在Navigator中以midlet变量来存放系统中惟一个MIDlet实例(也就是主程序),以display变量存放惟一一个Display实例(屏幕对象)。这两个变量必须在MIDlet构造方法中予以初始化。
另外,为每一个画面定义一个常数整数,并使用current变量记录目前画面。这个变量必须在MIDlet的startApp()之中给予初始化。
//Navigator.java
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.* ;
public class Navigator
{
//为每一个画面定义一个常数整数
final public static int MAIN_SCREEN = 1 ;
final public static int GAME_SCREEN = 2 ;
final public static int SET_SCREEN = 3 ;
final public static int GAME_HELP_SCREEN = 4 ;
final public static int SET_HELP_SCREEN = 5 ;
//存放系统中惟一个MIDlet实例
public static MIDlet midlet ;
//存放惟一一个Display实例
public static Display display ;
//current变量记录目前画面
public static int current ;
public static void show(Object obj)
{
switch(current)
{
case MAIN_SCREEN :
break ;
case GAME_SCREEN :
break ;
case SET_SCREEN :
break ;
case GAME_HELP_SCREEN :
break ;
case SET_HELP_SCREEN :
break ;
}
}
public static void flow(String cmd)
{
switch(current)
{
case MAIN_SCREEN :
break ;
case GAME_SCREEN :
break ;
case SET_SCREEN :
break ;
case GAME_HELP_SCREEN :
break ;
case SET_HELP_SCREEN :
break ;
}
}
}
流程控制器中,并没有包含版权声明画面以及启动画面的流程控制。这是因为两者都使用Alert实现,Alert具有“时间一到就自然跳回前一个画面”的特性,因此我们不另外对其作流程控制比较好。
show()方法用来统一显示出画面,其obj参数是为了特殊用途(例如有些画面在显示前需要做一些初始化)。而flow()方法用来统一判断程序的流程,利用current变量与cmd变量,能轻易的判断目前程序的运作方向。
三、 画面的设计
把流程的控制权交给Navigator.flow()方法,Navigator.flow()方法内部会使用Navigator.show()方法来装换画面。
1、 用户触发事件——>画面(事件处理方法)——>2.流程判断
2、 流程判断(流程控制器):
flow() ——> 设定current
flow() ——> 切换画面show()
并非所有画面都用到这个方法切换,比方说整个系统的第一个画面,或是当我们用到线程的时候,就可能会直接调用show()帮我们显示画面。
在调用show()之前,别忘了先设定current变量,才能让程序正确的转换画面。
每个画面的实现:
1、 辅助说明
import javax.microedition.lcdui.* ;
public class HelpScreen extends Form implements CommandListener
{
public HelpScreen(String c)
{
super("辅助说明") ;
append(c) ;
addCommand(new Command("返回",Command.BACK,1)) ;
setCommandListener(this) ;
}
public void commandAction(Command c,Displayable s)
{
Navigator.flow(c.getLabel()) ;
}
}
2、 版权声明
在系统之中只有一个,使用Singleton模式实现。
import javax.microedition.lcdui.* ;
public class CopyScreen extends Alert
{
private static Displayable instance ;
synchronized public static Displayable getInstance()
{
if(instance == null)
instance = new CopyScreen() ;
return instance ;
}
private CopyScreen()
{
super("版权声明") ;
setString("此应用程序之版权属于XYZ公司所有") ;
setType(AlertType.INFO) ;
setTimeout(Alert.FOREVER) ;
}
}
3、 主画面
import javax.microedition.lcdui.* ;
public class MainScreen extends List implements CommandListener
{
private static Displayable instance ;
synchronized public static Displayable getInstance()
{
if(instance == null)
instance = new MainScreen() ;
return instance ;
}
private MainScreen()
{
super("版权声明",IMPLICIT) ;
append("开始",null) ;
append("设定",null) ;
append("版权声明",null) ;
append("离开",null) ;
setCommandListener(this) ;
}
public void commandAction(Command c,Displayable s)
{
String cmd = getString(getSelectedIndex()) ;
Navigator.flow(cmd) ;
}
}
4、 设置画面
import javax.microedition.lcdui.* ;
public class SetScreen extends Form implements CommandListener
{
private static Displayable instance ;
synchronized public static Displayable getInstance()
{
if(instance == null)
instance = new SetScreen() ;
return instance ;
}
TextField url ;
Gauge volume ;
private SetScreen()
{
super("设定") ;
url = new TextField("请输入服务器位置","socket://192.168.0.3:99",40,TextField.URL) ;
append(url) ;
volume = new Gauge("音量",true,10,3) ;
append(volume) ;
addCommand(new Command("辅助说明",Command.HELP,1)) ;
addCommand(new Command("返回",Command.BACK,1)) ;
setCommandListener(this) ;
}
public void commandAction(Command c,Displayable s)
{
Navigator.flow(c.getLabel()) ;
}
}
5、 游戏画面
import javax.microedition.lcdui.* ;
public class GameScreen extends Canvas implements CommandListener
{
private static Displayable instance ;
synchronized public static Displayable getInstance()
{
if(instance == null)
instance = new GameScreen() ;
return instance ;
}
private GameScreen()
{
addCommand(new Command("辅助说明",Command.HELP,1)) ;
addCommand(new Command("返回",Command.BACK,1)) ;
setCommandListener(this) ;
}
public void commandAction(Command c,Displayable s)
{
Navigator.flow(c.getLabel()) ;
}
public void paint(Graphics g)
{
g.setColor(125,125,125) ;
g.fillRect(0,0,getWidth(),getHeight()) ;
g.setColor(0,0,0) ;
g.drawRect(10,10,60,70) ;
}
}
四、完成流程控制器
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.* ;
public class Navigator
{
//为每一个画面定义一个常数整数
final public static int MAIN_SCREEN = 1 ;
final public static int GAME_SCREEN = 2 ;
final public static int SET_SCREEN = 3 ;
final public static int GAME_HELP_SCREEN = 4 ;
final public static int SET_HELP_SCREEN = 5 ;
//存放系统中惟一个MIDlet实例
public static MIDlet midlet ;
//存放惟一一个Display实例
public static Display display ;
//current变量记录目前画面
public static int current ;
public static void show(Object obj)
{
switch(current)
{
case MAIN_SCREEN :
display.setCurrent(MainScreen.getInstance()) ;
break ;
case GAME_SCREEN :
display.setCurrent(GameScreen.getInstance()) ;
break ;
case SET_SCREEN :
display.setCurrent(SetScreen.getInstance()) ;
break ;
case GAME_HELP_SCREEN :
display.setCurrent(new HelpScreen((String)obj)) ;
break ;
case SET_HELP_SCREEN :
display.setCurrent(new HelpScreen((String)obj)) ;
break ;
}
}
public static void flow(String cmd)
{
switch(current)
{
case MAIN_SCREEN :
if(cmd.equals("开始"))
{
current = GAME_SCREEN ;
show(null) ;
}else if(cmd.equals("设定"))
{
current = SET_SCREEN ;
show(null) ;
}else if(cmd.equals("版权声明"))
{
display.setCurrent(CopyScreen.getInstance()) ;
}else if(cmd.equals("离开"))
{
midlet.notifyDestroyed() ;
}
break ;
case GAME_SCREEN :
if(cmd.equals("辅助说明"))
{
current = GAME_HELP_SCREEN ;
show("游戏的操作方式") ;
}else if(cmd.equals("返回"))
{
current = MAIN_SCREEN ;
show(null) ;
}
break ;
case SET_SCREEN :
if(cmd.equals("辅助说明"))
{
current = SET_HELP_SCREEN ;
show("设定方式") ;
}else if(cmd.equals("返回"))
{
current = MAIN_SCREEN ;
show(null) ;
}
break ;
case GAME_HELP_SCREEN :
if(cmd.equals("返回"))
{
current = GAME_SCREEN ;
show(null) ;
}
break ;
case SET_HELP_SCREEN :
if(cmd.equals("返回"))
{
current = SET_SCREEN ;
show(null) ;
}
break ;
}
}
}
四、 MIDlet主程序设计
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class FlowControl extends MIDlet
{
boolean init = true ;
public FlowControl()
{
Navigator.display = Display.getDisplay(this) ;
Navigator.midlet = this ;
}
public void startApp()
{
Navigator.current = Navigator.MAIN_SCREEN ;
Navigator.show(null) ;
if(init)
{
Alert al = new Alert("片头画面") ;
al.setType(AlertType.CONFIRMATION) ;
al.setTimeout(5000) ;
Navigator.display.setCurrent(al) ;
init = false ;
}
}
public void pauseApp()
{
}
public void destroyApp(boolean con)
{
}
}
在系统每个地方都会用到MIDlet实体和Display实体,避免重复编写相同代码,这两个变量必须在MIDlet的构造方法中给与初始化。
另外,current变量必须在startApp()之中予以初始化。
init()变量判断startApp()是否第一次执行,只有第一次执行才显示启动画面。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics