用惯的J2ME高级UI而还没有接触过低级UI的developer一定不会习惯跟Canvas的初夜!因为他没有Form那么多可以触摸到风情万种,也没有Item的独特风味,有的只是一个只画笔一个画布,一些只有名字的事件响应方法,更奇怪的事情是你不能手动的调用paint()去做你想做的事情,一切你都只能任凭系统摆布!今日话题 — J2ME Canvas 开发小解 [请注意不是小姐,你可以理解为详解] !
所要将的几个问题:
一.首先是一些朋友的疑惑:
1.如何去实现一个J2ME Canvas的输入框?想TextField那种!
从这里开始基本就进入我们的正题了。在现在这个人工智能技术还相当不成熟的计算机时代,用户交互最重要的凡是无非就是向计算机系统提供数据和指令让其返回执行结果以供我们使用。所以,但你需要开发一个J2ME程序的时候,数据交互是必不可少的,当受够了LCDUI那丑陋的界面(或者说不个性的界面,实际上就是系统调用嘛)的时候,Canvas和CustumItem是一个最容易想到的办法,实际上要从底层实现的话也只有这个方法。由于CusomItem还是摆脱不了Command的束缚,所以Canvas是你最自由的空间,但是自用永远都是属于强者的。不要奢望很简单的一个new就能完成。NOW BEGIN …
要想在Canvas里面直接实现一个类似于TextField那样的输入框是不可能的!请注意:它的意思是“画布”,也就是说之能画东西。不要着急,会给你笔的 — Graphics ! 要如何去实现一个输入框呢? 这样的: 画一个矩形 — 响应用户点击事件 — 打开TextBox — 完成输入 — 返回输入结果 — 将得到的结果画在刚才画的那个矩形里面 ! 搞定
也看到有的说,在底层自己去实现一个输入法。我觉得这是一个极其愚蠢的说法,我用ARM处理再加上一个液晶显示器完成一个输入的功能,其调用过程是相当繁琐的。呵呵,所以想要自己实现输入法并显示字体的暂时打住吧。
2.如何在Canvas里面实现一个滚动条?用起来UE(User Experance)一定能好一点!
要完成一个滚动条,需要做两件事: 1.判断是否需要滚动条;2.去绘制这个滚动条 。 Canvas不会在你需要ScrollBar的时候自己出现,她永远没有isNeeded()这种好事提供给你。下面将会有一些示例性代码。
3.如何去实现一个用J2ME Canvas做到菜单?这是必须的。
所谓菜单,也就是屏幕上面显示的像菜单的东西罢了,说到底还是要自己画的,只是选择画的位置不一样而已。好了,稍候给出实现方式。
4.Canvas中输入法的实现?!好难的问题。
上面已经说了,可能性不太大,要不自己去看J2ME的源代码,可以试试。(去参考当打开TextField的时候都使用了些什么系统调用,哈哈)
5.Canvas之间为啥会出现重叠现象?如何去做到让一个Canvas隐藏起来?
出现Canvas图像重叠这个问题其实是很简单的,因为你都是画在屏幕上面的啊,而屏幕只有一个,再因为Canvas是透明的,呵呵。这个是默认值。所以,一般情况下如果你不需要其他颜色的画就是在初始化的时候将Canvas全部填充为白色,如下:
[方法一]int i = 0 ;
while(i < this.getHeight()){
g.drawLine(0,i,this.getWidth(),i,Graphics.Top|Graphics.LEFT);
}
[方法二]
g.fillRect(0,0,this.getWidth(),this.getHeight());
6.如何退出一个Canvas?或者说怎么从一个Canvas切换到另外一个Canvas?
Canvas类没有提供类似于hidden()这样的方法,而是只提供了一些检查型的方法,例如 isShow() 。而还有例如showNotify()这样的方法都是系统去调用的。这个通常是当你调用setCurrent()的时候系统回去将需要显示的现实处理。所以,要做Canvas之间实现切换无非就是这么做: setCurrent(ANOTHER Displayable) 。
二.小总结一下,阐释一下疑惑:
造成诸多的不可操作和操作繁琐的原因无非就是一个:JAVA希望你自由。你要自由的话,那就自己去画吧,想怎么画怎么画,想怎么做怎么做。一切都要你自己去实现。
【FIRST】J2ME Canvas提供的一些我们要用到的方法:
在J2ME游戏编程中,Canvas类是最常用的类之一,该类提供了获得手机屏幕属性、绘制界面以及事件处理等很多实用的功能,下面就系统的介绍一下该类的使用,并结合实际说明一下在实际的使用过程中需要注意的一些问题。
Canvas类是Displayable的子类,主要用来需要处理低级事件,例如键盘按键事件等,以及需要绘制屏幕的程序。在实际的使用过程,一般都通过继承Canvas来利用该类提供的功能。Canvas类是一个抽象类,继承该类的时候必须覆盖paint方法。
Canvas类的功能主要包含以下几类:
1、 获得手机屏幕属性
getHeight——获得屏幕可用高度
getWidth——获得屏幕宽度
isDoubleBuffered——是否支持双缓冲
hasPointerEvents——是否支持指针设备
hasPointerMotionEvents——是否支持指针动作,例如拖拉事件
hasRepeatEvents——是否支持重复按键
实际开发过程中,可以在程序中直接调用这些方法,通过返回值获得相应的属性信息。
2、 绘制方法
paint——绘制方法
repaint——重新绘制方法
paint方法需要在界面类中覆盖,然后就可以书写功能,而再需要重新绘制的时候手动调用repaint方法。
3、 事件处理
低级用户界面的事件处理分为两种:按键事件和指针事件(处理触摸屏手机的低级事件)。
支持按键事件的方法主要有三个:
keyPressed——按键按下的事件
keyReleased——按键释放的事件
keyRepeated——重复按键的事件
支持指针事件的方法也有三个:
pointerPressed——指针设备按下事件
pointerReleased——指针设备释放事件
pointerDragged——指针设备拖拉事件
在实际的编程中,只需要在界面类中覆盖这些方法,然后在方法的内部书写代码即可,在发生这些事件的时候,系统会自动调用这些方法。
4、 其他方法
getGameAction——将按键映射成游戏动作
getKeyCode——将游戏动作转换为键值
getKeyName——将键值转换为按键名称
hideNotify——在Canvas界面被隐藏的时候系统自动调用该方法
showNotify——当Canvas界面显示的时候系统自动调用该方法
serviceRepaints——强制系统重新绘制
以上方法除了hideNotify、showNotify需要在子类中覆盖,系统会自动调用以外,其他的方法都可以直接调用。
5、 系统的熟悉主要分为两个部分:
按键的键值
KEY_NUM0、KEY_NUM1、KEY_NUM2、KEY_NUM3、KEY_NUM4、KEY_NUM5、KEY_NUM6、KEY_NUM7、KEY_NUM8、KEY_NUM9分别对应手机键盘的0-9数字键,KEY_STAR对应*号键,KEY_POUND对应#号键。各个功能键的键值在Canvas类没有进行定义,所以各个厂商,甚至厂商的不同型号手机之间,的键值都有所不同。但是功能键的键值均小于0。
游戏动作
UP、DOWN、LEFT、RIGHT和FIRE,分别对应上、下、左、右和确定键,在实际的手机中一般分别对应2、8、4、6和5键以及功能键中的方向键。GAME_A、GAME_B、GAME_C、GAME_D分别对应游戏中的A、B、C和D键,分别映射成手机上的1、3、7、9键,或者是7、9、*和#键。
在实际的事件处理中,使用游戏可以达到在不同的手机之间通用。
在MIDP2.0中,又新增了两个方法,分别是:
setFullScreenMode——控制屏幕是全屏幕显示还是一般模式显示
sizeChanged——当屏幕尺寸变化的时候,系统会自动调用该方法。
上面系统的介绍了Canvas类提供的功能,下面就介绍一下它的使用。当调用Display对象的setCurrent方法显示Canvas界面时,系统首先调用Canvas对象的构造方法,然后调用paint方法实现绘制,这样我们就可以看到实际的界面了。而以后,当界面坐标发生变化以后,必须手动调用repaint方法实现绘制。
以下是一些在实际的编程过程中,需要注意的一些问题:
坐标参数化或者根据屏幕的宽度和高度生成坐标,从而提高界面的移植性。
在程序运行过程中,paint方法经常被反复调用,所以一般在该方法中只放置绘制的代码,而把逻辑处理的代码放在别的位置。
在进行低级事件处理代码编写中,对于游戏按键,尽量使用游戏动作进行编程,其他按键才直接使用键值,从而提高事件处理代码的移植性。
因为hideNotify和showNotify方法会在界面隐藏和重新显示时,被系统自动调用,所以可以覆盖这两个方法,在其中实现暂停的逻辑。
尽量使用双缓冲技术,避免屏幕的闪烁,除非手机不支持双缓冲。
显示其他界面的时候,注意要关闭线程,并释放资源。
在Canvas里面永远没有Form里面那些append啊,addxxxx啊之类的方法,因为那样你就不自由了,所以JAVA不会去做一些束缚你的事情,感谢上苍吧!
三.J2ME Canvas 开发小试牛刀:
1.实现Loading效果(进度加载):
要实现Loading效果,你必须要做的一件事情就是使用多线程!这样用起来比较爽。
….
//先画一个小矩形,圆角不圆角你说了算
g.fillRect(10,10,80,20); // 画了一个宽80,高20的矩形,做进度条很不错哦
new Thread(new Runnable(){
public vodi run () {
g.fillRect(10+delta,10,20,20); //画一个X坐标值为动态的小矩形在上面那个大矩形中,你的loading效果出来怎么样就完全看你的动态X和小矩形的宽度如何设定了
}
}).start();
….
2.实现一个Canvas表单(输入框,按钮,图像):
这里就可以涉及到事件响应了,Canvas给我们提供了keyPressed(int key)来响应用户的按键 。
protected void paint(){
g.drawRect(20,20,80,30);
}
protected void keyPressed(int key){
int action = getGameAction(key);
switch(action){
case Canvas.FIRE : EventHandle.doInput(); break ;
}
repaint();
}
上面这段代码展示了一个如何去实现一个输入事件的响应,也就是当用户按下通常的OK键的时候会去做输入这件事情,至于怎么做就看你的了,不过一般情况就是去打开一个TextBox完成输入之后再把用户的输入绘制到矩形里面。
3.锚点:
在画布中锚点实际上就是坐标原点,取值有类似于这些:
【 Graphics.Top|Graphics.LEFT 】
【 Graphics.Top|Graphics.RIGHT 】
【 Graphics.BOTTOM|Graphics.LEFT 】
【 Graphics.BOTTOM|Graphics.RIGHT 】
【 Graphics.VCENTER|Graphics.HCENTER 】
【 Graphics.HCENTER|Graphics.TOP 】
【 Graphics.HCENTER|Graphics.BOTTOM 】
【 Graphics.VCENTER|Graphics.LEFT 】
【 Graphics.HCENTER|Graphics.RIGHT 】
根据意思你就会明白是哪里了,这些值必须成对使用,因为是用的逻辑运算符 “|” 所以,每一对值的次序也就不重要了。
4.如何去处理不同手机平台键值不一样的问题:
由于手机种类实在太多,你不可能完全了解,或者每一个程序都为每一种机型开发一个版本。那样是不现实的,也是效率低劣的。所以,我的做法是,让使用者来确定。哈哈,方法就是首次运行的时候让用户告诉我们每个键值是多少,然后记录下来就OK啦,哈哈,还不用移植。
5.[引用]关于J2ME中双缓冲技术:
双缓冲技术是编写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, <a href="http://www.sunjianyes.cn/Article/tag/graphics">Graphics</a>.VCENTER|Graphics.HCENTER);
}
public void paint(Graphics g)
{
g.drawImage(buffer,0,0,g.TOP|g.LEFT); //将缓冲区上的内容绘制到屏幕上
}
}
编译、运行程序,其结果和前面完全相同,但是却采用了双缓冲技术。对于双缓冲的使用,可以总结出以下几点。
* 定义一个Graphics对象bg和一个Image对象buffer,按屏幕大小建立一个缓冲对象赋给buffer,然后取得buffer的 Graphics对象赋给bg。在这里,Graphics对象可以理解为缓冲的屏幕,Image对象则可当成缓冲屏幕上的图片。
* 在bg(缓冲屏幕)上用drawImage()和drawString()等语句画图,相当于在缓冲屏幕上画图。
* 调用repaint()语句,它的功能是告知系统调用paint()来完成真实屏幕的显示。这里需要注意的是,paint()是一个系统调用语句,不能手工调用,只能通过paint()语句来调用。
* 在paint(Graphics g)函数里,将buffer(缓冲屏幕上的图片)画到真实屏幕上。
以上的步骤虽然看似繁琐,但是本身在不支持双缓冲机制的时候是必须的,回过头来看效果还是很不错的。如果想在屏幕上显示什么东西,只要画在bg上,然后调用repaint()将其显示出来就可以了。
6.比较好的调用paint(Graphics g)的方式:
paint(Graphics g) {
init(g);
yourPainter_1(g);
yourPainter_(g);
}
init(Graphics g){
// ......
}
yourPainter_1(Graphics g){
// ......
}
yourPainter_2(Graphics g){
// ......
}
上面所示这种是我认为比较好的一种使用paint方法的办法,推荐下!
7.关于J2ME Graphics的画笔
g.setStrokeStyle(Graphics.DOTTED);
g.setStrokeStyle(Graphics.SOLID);
Graphics只有这两种画笔,一个视线一个点线,需要什么就是什么。