|
第4章 MIDP低级UI的使用
我们从javax.microedition.lcdui.Canvas开始了解我们的低级UI,我们要用到低级UI必须要继承Canvas这个抽象类,在 Canvas的核心是paint()这个方法,这个方法做是负责绘制屏幕上的画面,每当屏幕需要重新绘制时,就会产生重绘事件时,系统就会自动调用paint(),并传入一个Graphics对象。
任何时候我们都可以通过调用reapaint()方法来产生重绘事件,它有两个方法,一个需要四个参数,分别用来指示起始坐标(X,Y),长宽,另一个则不需要任何参数,代表整个画面重新绘制。
我们可以通过getWidth()和getHeight() 方法获得Canvas的当前范围大小。每当Canvas 范围大小发生变化时,就会自动调用Canvas类的 sizeChanged()方法。
在低级UI里,我们可以直接把Graphics渲染到屏幕上,也可以在屏幕外合成到一个 Image中,已渲染的图形具体是合成Image还是显示到屏幕上,要看这个Graphics具体的来源而定,而渲染到屏幕上的Graphics对象将被送到paint()方法中来进行调度,这也是显示在屏幕上的唯一的途径,仅在paint()方法的执行期间这个应用程序可以对Graphics进行操作,至于要渲染到Image中的Graphics对象,当需要调用它的时候,可以通过Image.getGraphics()方法来取得相应的Graphics,它将可以被应用程序一直占有,在paint()方法运作的任何时候渲染到屏幕上,这也为我们在对不支持DoubleBuffered的手机开发提供了一些思路,可以通过Image来自行设计双缓冲区,避免图像出现所谓的撕裂现象。
4.1 低级API与低级事件的联系与高级UI相比,低级UI就自由很多,任何时候我们可以调用repaint()产生重绘事件,调用完了repaint()会立刻返回,调用paint()回调函数则是由另一个专门的线程来完成。
底层事件大致可分为三类:Press Events(按键事件),Action Keys(动作按键,PointerEvents(触控事件)。
本节我们将围绕这三个主题来介绍一下这种事件的用法:
按键事件的几个核心方法为:keyPressed(),keyReleased(),keyRepeated(),当按键按下时会触发keyPressed(),当松开按键时,会触发keyReleased(),当长时间按住按键时会触发keyRepeated(),但是RepeatEvents不是JTWI要求强制支持的,所以使用之前要进行测试,看设备是否支持。在Canvas里面我们每按下一个按键都会触发keyPressed()函数,并传入相应位置的整数值,我们在MIDP规范中可以很容易的发现,KEY_NUM0——KEY——NUM9十个常数分别代表键盘上的0-9,还有两个功能键,KEY_STAR,KEY_POUND,如果我们传入的值小于0,代表我们传入了不合法的keycode,某些机器上还支持连续按键响应,但这并不是JTWI规定要支持的,所以我们在进行实际开发之前一定要用我们前面讲到的hasRepeatEvents()方法来进行判定。
动作按键主要针对游戏来设计的,在API中定义了一系列的动作事件:UP,DOWN,LEFT,RIGHT,GAME_A,GAME_B,GAME_C,GAME_D,当按下这些按键时会映射到我们自己为每个按键事件编写的方法,来完成一些动作。不过我们在MIDP2.0里我们已经有专门的游戏开发包了,所以我在这里就不重点介绍了。
触控事件主要面向高端设备,并非JTWI要求强制支持的,其核心方法为:pointerPressed(),pointerReleased(),pointerDragged(),分别对应我们通常所用的移动设备手写笔的点,击,拖拽几个动作,我们在这三个方法里可以定义相应的事件处理函数。在索爱P910C这样的高端手机上,支持屏幕的触控事件,我们在屏幕上点击,可以引发pointerPressed()函数,并传入当时位置的坐标,放开后,会引发pointerReleased()函数,同样也会传入坐标,具体的使用方法和keyPressed()以及keyReleased()大同小异,在后面的章节将会有对键盘及触控屏幕事件的详细叙述,同时大家可以参考一下WTK的说明文档获得比较详细的方法使用规则
4.2 重绘事件及Graphics入门4.2.1 坐标概念我们在MIDP程序设计中用到的坐标系和一般我们平时用到的坐标系不一样,可见下图:
这是我们在绘制图象时要注意的。
下面我们来讲一讲Graphics这个对象,我们可以把它当作一个白纸,只要调用这个方法,我们就可以运用自己的想象力在这张白纸上画出自己想要的图案。
4.2.2 颜色操作我们可以在WTK的控制台看到下面这个程序运行以后显示其Canvas的RGB值和灰度等参数,读者可以运行下面这个程序来获得对Graphics这个对象的初步了解。
下面我用一段简单的代码来说明一下这个Graphics对象的应用:
import javax.microedition.lcdui.*; import javax.microedition.midlet.*;
public class test extends Canvas { public void paint(Graphics g) { g.setColor(255,255,0); g.fillRect(0,0,getWidth(),getHeight()); int c=g.getColor(); int dc=g.getDisplayColor(g.getColor()); System.out.println("当前画面的颜色为:"+Integer.toHexString(c)); System.out.println("当前画面的R值为:"+g.getRedComponent()); System.out.println("当前画面的G值为:"+g.getGreenComponent()); System.out.println("当前画面的B值为:"+g.getBlueComponent()); System.out.println("当前画面的显示颜色为:"+Integer.toHexString(dc)); System.out.println("当前画面的灰度为:"+g.getGrayScale()); } }
需要大家注意的是R,G,B的值只能在0——255之间,不可以超出这个范围,另外我们可以直接用0x00RRGGBB格式进行颜色的调配。 4.2.3 绘图操作Graphics类提供的大量的绘图操作,这里给出了相关操作的方法列表供读者参考:
主要是一组和绘画和填充的方法。请参考API的查看更多细节。
下面我们就来谈一谈如何用Graphics中线形的概念。如果我们需要绘制一条直线,我们可以调用drawLine()方法,需要定义其开始坐标和结束坐标,共四个参数,同时,Graphics提供两种形式的线条,一个是虚线,即Graphics.DOTTED,一个是实线,即Graphics.SOLID。
同样我们给出一段代码供大家参考:
import javax.microedition.lcdui.*; import javax.microedition.midlet.*;
public class test2 extends Canvas { public void paint(Graphics g) { g.setColor(255,255,255); g.fillRect(0,0,getWidth(),getHeight()); //在以后的实际开发当中会经常用到上面两行代码,它们的作用是进行画面的初 //化 g.setColor(255,0,0); g.drawLine(1,1,100,10); g.setStrokeStyle(Graphics.DOTTED); //虚线 g.setColor(125,125,125); g.drawLine(10,10,100,100); g.setStrokeStyle(Graphics.SOLID); //实线 } }
用类似的方法,我们可以实现用Graphics的drawRect()和drawRoundRect()方法来绘制矩形和圆角矩形,我们也给出一段代码,让大家仔细观察一下两种矩形的区别: import javax.microedition.lcdui.*; import javax.microedition.midlet.*; public class test3 RectTestCanvas extends Canvas { public void paint(Graphics g) { clear(g) ; g.setColor(255,0,0) ; g.drawRect(5,5,100,20); g.setColor(0,255,0) ; g.fillRect(5,30,100,20);//fillRect()和drawRect()方法的区别在于一个填充 //一个不填充 g.setColor(0,0,255) ; g.drawRoundRect(5,55,100,20,20,20); g.setColor(255,0,255) ; g.fillRoundRect(5,80,100,20,20,20); } public void clear(Graphics g) { //把屏幕清成白色 g.setColor(255,255,255); g.fillRect(0,0,getWidth(),getHeight()); } } }
4.3 Canvas与屏幕事件处理Canvas本身有两种状态,一种是普通默认情况下的,一种是全屏状态,可以用setFullScreenMode()方法来对其设定,两者之间的区别在于当我们使用全屏幕状态的时候,Title、Ticker以及我们的Command都无法在屏幕上显示。
当我们调用setFullScreenMode()的时候,不管是什么模式,都会调用seizeChanged()这个方法,并传入屏幕的高度和宽度作为其参数。
对于某些突发事件,比如说来电等等,屏幕会被系统画面所覆盖的时候,就会调用hideNotify()这个方法,当恢复原状时,就会调用我们原本的画面,那么系统就会同时调用showNotify()这个方法。在实际操作过程当中,应该覆写这两个方法,以便在可见性变化时,使程序做出相应的反应,Canvas会在它被显示的时候自动调用paint()方法,所以我们不必去调用repaint()方法。
下面给出一段代码,让大家体会一下如何在实际开发过程当中妥善处理屏幕事件:
import javax.microedition.lcdui.*; import javax.microedition.midlet.*; public class test4 extends Canvas implements CommandListener { public test4() { setTitle("全屏幕测试") ; setTicker(new Ticker("Ticker ")) ; addCommand(new Command("全屏幕",Command.SCREEN,1)) ; addCommand(new Command("正常",Command.SCREEN,1)) ; setCommandListener(this) ; } public void paint(Graphics g) { g.setColor(125,125,125);//灰色 g.fillRect(0,0,getWidth(),getHeight()); g.setColor(0,0,0);//黑色 g.drawLine(10,10,150,10); } public void commandAction(Command c,Displayable s) { String cmd = c.getLabel(); if(cmd.equals("全屏幕")) { setFullScreenMode(true) ; }else if(cmd.equals("正常")) { setFullScreenMode(false) ; }
} protected void sizeChanged(int w,int h) { System.out.println("改变后的宽度:"+w) ; System.out.println("改变后的高度:"+h) ; } protected void hideNotify() { System.out.println("屏幕被系统遮蔽") ;//会在WTK控制台中显示, //读者需要注意 } protected void showNotify() { System.out.println("屏幕显示在屏幕上") ; } }
从截图1和截图3可以看出全屏幕和普通模式的区别,全屏幕的Canvas的显示区域覆盖了原来显示标题和Ticker的地方。
4.4 键盘及触控屏幕事件的处理如果我们需要在Canvas里处理我们的按键事件,我们必须覆写 Canvas的keyPressed(),keyReleased()和keyReapeated()这三个方法,其中keyReapeated()方法JTWI并未做硬性规定,所以我们在开发的时候一定要用Canvas.hasRepeatedEvents()方法来进行实际的侦测,当按下按键时会触发keyPressed()方法,松开时会引发keyReleased()方法 ,长时间按住的话则会引发keyRepeated()方法,JTWI硬性规定MIDP2.0的目标设备必须硬性支持ITU-T的电话键盘,即必须使数字0到9,“*”,“#”必须能在 Canvas中得到定义,当然我们也可以扩充其他按键,但是这样对程序的移植就会有影响,下面我们通过代码来看看如何在实际开发中运用上面提到的三个方法。
按下前:
按下后:
代码: import javax.microedition.lcdui.*; import javax.microedition.midlet.*; public class test5 extends Canvas implements CommandListener { public test5() { addCommand(new Command("测试",Command.SCREEN,1)) ; setCommandListener(this); } boolean pressed = false ; 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);
} } public void commandAction(Command c,Displayable s) { System.out.println("Command Action"); } protected void keyPressed(int keycode) { System.out.println("Key Pressed"); pressed = true ; repaint() ; } protected void keyReleased(int keycode) { System.out.println("Key Released"); pressed = false ; repaint() ;
} }
对于触控事件(pointer events)的方法,应用程序可以通过覆写pointerPressed(),pointerReleased()和pointerDragged方法,(分别对应于手写笔的按下,松开,拖拽三个动作)其处理过程和按键处理几乎一致,所以这里不螯述。 4.5 Graphics相关类在这一节里面我们通过设计一个稍微复杂一点的动画来体现Graphics在实际开发当中带来的便利,在这里给出几段曾经对我们启发很大的代码,通过围绕这几段代码进行分析,来掌握Graphics在实际开发当中的作用。
4.5.1 Image类前面我们谈到了双缓冲区问题,我们先就Image这个类来谈一谈。
在介绍Image之前,先介绍几个比较基础的概念,无论是图像还是文字在Graphics中都是通过锚点(anchor points)来控制它们具体的方位,对于Image而言有如下几个锚点常量:LEFT,RIGHT,HECENTER,TOP,VCENTER,BOTTOM, BASELINE。其具体位置对应如下:
Image分为可变和不可变两种类型的,不可变的Image是从资源文件,二进制数据,RGB数值,及其他Image直接创建的,一旦创建完成,Image就无法再变化。不可变的Image通过Image.createImage(String name) 方法从指定的路径中读取需要创建Image所必须的数据,注意参数中的字符串必须以“/”打头,并且包括完整的名称。
可变的Image以给定的大小创建,它是可以修改的,可变的Image由Image.createImage(int width,int height)方法来创建,需要给定长宽,Image的其他显示特性和机器的显示屏完全一致。
我们前面提到了撕裂现象,它产生的原因是因为显示屏在显示图像前都会先参照影象内存,然后当绘制速度慢到一定程度时,显示在屏幕上的画面会由前一帧画面的一部分和后一帧画面的一部分组成,这样造成图像“撕裂”,为了解决这个问题,手机厂商可以从硬件上支持DoubleBuffer,即双缓冲区,这样显示屏绘图的时候都可以使用一个影象内存来绘图,另一个影象内存来进行程序绘图,这样交叉进行,可以避免画面撕裂的产生。
如果硬件厂商并未在硬件上支持DoubleBuffer怎么办呢?我们可以从程序上着手,自己设计一个双缓冲区。
代码片段如下:
import javax.microedition.lcdui.*; import javax.microedition.midlet.*; public class test6 extends Canvas implements Runnable,CommandListener { Command start = new Command("开始",Command.OK,1) ; Command stop = new Command("停止",Command.STOP,1) ; private Image offscreen ; public test6) { addCommand(start); setCommandListener(this) ; if(isDoubleBuffered()) { System.out.println("支持双缓冲区"); }else { System.out.println("不支持双缓冲区,启动自制双缓冲区"); offscreen = Image.createImage(getWidth(),getHeight()); } } public void paint(Graphics g) { if(isDoubleBuffered()) { System.out.println("On-Screen绘图"); clear(g); paintAnimation(g,100,10,r) ; paintCross(g,x,y,length) ; }else { System.out.println("Off-Screen绘图"); Graphics offg = offscreen.getGraphics() ; clear(offg) ; paintAnimation(offg,100,10,r) ; paintCross(offg,x,y,length) ; g.drawImage(offscreen,0,0,0); }
} public void clear(Graphics g) { //把屏幕清成白色 g.setColor(255,255,255); g.fillRect(0,0,getWidth(),getHeight()); } int r = 0 ; public void paintAnimation(Graphics g,int x,int y,int l) { g.setColor(0,0,0); g.drawRect(x,y,l,l); } int x =50 ; int y =50 ; int length = 5 ; 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); } boolean conti = false ; public void commandAction(Command c,Displayable s) { String cmd = c.getLabel() ; if(cmd.equals("停止")) { conti = false ; removeCommand(stop); addCommand(start) ; }else if(cmd.equals("开始")) { removeCommand(start); addCommand(stop) ; conti = true ; Thread t = new Thread(this); t.start(); } } int rate = 50 ; //每1/20秒画一次 public void run() { long s = 0 ; long e = 0 ; long diff = 0 ; while(conti) { s = System.currentTimeMillis() ;
r++; if (r > 10) r = 0; repaint(); serviceRepaints() ;
e = System.currentTimeMillis() ; diff = e-s ; if(diff<rate) { try { Thread.sleep(rate-diff); }catch(Exception exc){} }
} } protected void keyPressed(int keycode) { switch(getGameAction(keycode)) { case Canvas.UP : y = y-2 ; break ; case Canvas.DOWN : y = y+2 ; break ; case Canvas.LEFT : x = x-2 ; break ; case Canvas.RIGHT : x = x+2 ; break ; } repaint(); } }
需要提醒各位读者注意的是Image.createImage()非常浪费内存,我们最好能够尽量重复使用它。
4.5.2 字体类Graphics中还提供了对了对字体的控制方法,每个Graphics都有一个Font对象与其关联,来进行文字的渲染操作,调用其类方法setFont(null),即可使字体恢复到默认状态,对于具体的参数,Font提供了以下常量,来控制Font的属性:
字体大小:SMALL、MEDIUM、LARGE 字体外观:PROPORTIONAL、MONOSPACE、SYSTEM 字体风格:PLAIN、BOLD、ITALIC、UNDERLINED
通过charWidht(),charsWidth(),stringWidth(),substringWidth()来获得字符串,字符,字符集合的宽度,垂直方面则可以参考getHeight()和getBaselinePosition()方法获得。
当你不对Font进行设定时,机器会自动从设备中选择最合适的 Font属性。
|