- 浏览: 431378 次
- 性别:
- 来自: 深圳
最新评论
-
ahong520:
有问题,校验http://a.a-a.html?a=d 为fa ...
JS之正则表达式验证URL -
李_俊:
汗哒哒,原路径在这里:http://www.iteye.com ...
JAVA6动态编译的问题 -
wcl694216530:
...
Java压缩zip文件 -
dingran:
找到原因了,用2.2的模拟器就可以了,4.0的有问题。
向android模拟器中复制文件报out of memory错误解决 -
dingran:
哎,我也遇到了这个问题啊,很蛋疼啊。错误如下:emulator ...
向android模拟器中复制文件报out of memory错误解决
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()是否第一次执行,只有第一次执行才显示启动画面。
相关推荐
j2me入门教程j2me入门教程j2me入门教程j2me入门教程j2me入门教程
J2me中文教程.pdf J2me中文教程.pdf
j2me教程j2me教程j2me教程j2me教程
J2ME培训教程,以前学JJ2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程J2ME培训教程2ME发的一个PPT,内容还可以,需要的朋友不要客气J2ME...
J2ME中文教程 不错的学习资料,刚开始学习的可以参考下!
J2ME中文教程 适合初学者学习的好书 很好很强大!多多下载 多多学习 共同进步!
J2ME 中文教程
J2ME中文教程 J2ME中文教程
j2me中文教程
J2ME开发教程及笔记
本教程首先介绍了 j2me 开发体系,然后深入各个MIDP2.0 API,最后是搭建平台的知识。 第一章 “J2ME 技术概述”让你在学习J2ME 以前知道什么是J2ME。本章介绍了J2ME 平 台的体系结构和MIDlet 生命周期的概念。为以后...
j2me中文教程 cldc,cdc,gui, game ...
J2ME中文教程 J2ME中文教程 J2ME中文教程 J2ME中文教程
j2me 开发教程全集 提供一些常用的初级代码 注意事项
全面覆盖MIDP2.0,丰富的示范代码,...本教程是为了学习J2ME MIDP 技术的开发者准备的。尽管本教程内容涉及初学阶段,但本教程不仅仅是一本入门的读物,很多的内容的详细程度对于有经验的开发者来说,也是很多脾益的。
J2ME的中文教程,值得看一下!
J2me学习的好帮手,详解,细节,自认为很有帮助,解密版,
j2me开发教程全集.rar
J2ME中文教程J2ME开发下载,J2ME中文教程J2ME开发下载PDF