MIDlet API 2.0学习之:javax.microedition.lcdui (二)

Device-ProvidedOperations

在许多高级UI类中都会有一些在用户界面可用的附加的操作。这些附加操作对于应用程序来说是不可见的,只对最终用户开发。这个操作集的可用性完全依赖于特定设备的UI设计。例如:在有ITU-T小键盘的设备需要一个允许让用户改变在字母和数字间文本输入模式的操作。更复杂的输入系统需要额外的操作。最终用户不需要明白哪个操作是由系统提供哪个是由应用程序提供。不是所有操作在每个具体实现的应用程序里都是可用的。例如:一个有字联想文本输入模式的系统通常会提供在TextBox类中的附件操作。没有这种文本输入的系统就不会有类似的操作。

一些操作在所受设备上都是可用的,但是这些操作的实现方式在不同设备上会有很大差别。这种操作的例子是:List元素和Form项之间的导航机制、List元素的选择方式、一个文本编辑器中移动输入位置等等。一些设备不允许对Item的值的直接修改,但是可以让用户切换到画面以外的编辑器来实现这种修改操作。List元素的选择可以像这个例子:用专用的“Go”或“Select”或一些其它的键来实现。有些设备没有专用的选择键,当需要选择时需要用其它方式。

在使用一个专用选择键来完成选择操作的设备上,这个键不会经常需要一个lable来显示它。在任何键意义明显的情况下,这种程序实现都能准确的使用这个键。例如:如果用户使用一个相互独立的选项集合,这个选择键会明显地选择这些选项中的一项。如果一个设备内没有一个专用的键,那么它可能会用一个需要lable的软按键来完成这个选择操作。系统提供为IMPLICIT类型的List设置一个select-command的能力和为一个Item设置defaultCommand的能力因此应用程序可以为这个操作设置lable,从而应用可以在操作触发时受到通知。

High-LevelAPIforEvents

高级API的事件处理是基于一个监听模型的。Screens和Canvases会拥有Command的监听器。一个监听器对象必须实现一个CommandListener接口,该接口有一个方法:

voidcommandAction(Commandc,Displayabled);

如果Screen和Canvas已经绑定了Command并有一个注册了的监听器,那么这个应用就可以得到这些事件。如果应用采用了单一监听模型(unicast-versionofthelistenermodel),那么Screen和Canvas只能在同一时刻有一个监听器。

Form中的Item也有一个状态改变的坚挺接口。定义在接口ItemStateListener的方法voiditemStateChanged(Itemitem)在当一个交互的Gauge、ChoiceGroup或TextField的值改变时被调用。在每个状态改变之后都调用监听器是不可预计的。如果一个Item的值已经被改变,这个监听器会在它为其它Item调用或一个Command被传递到Form的CommandListener之前某个时候为这个改变调用。建议状态改变监听器至少在Form字段失去焦点时被调用。监听器只会在字段值真正被改变时调用。

Low-LevelAPIforEvents

低级图形和事件有如下方法来处理低级按键事件:

public void keyPressed(int keyCode);    
public void keyReleased(int keyCode);    
public void keyRepeated(int keyCode);

最后一个方法,keyRepeated,不是在所有设备里都是必须的。应用程序可以调用Canvas的如下方法来检查该动作的可用性:

public static boolean hasRepeatEvents();

这个API需要ITU-T小键盘有标准的键码(0-9,*,#),但是API不需要小键盘布局。尽管某种API实现可以提供额外的键,但是应用程序依赖于这些键会丧失可移植性。

此外,Canvas类有处理抽象游戏事件的方法。某种API实现会吧该设备上所有合适的键映射到这些键事件上。例如:一个中间有四通式导航和一个选择键设备可以使用这些键,但是简单设备也许会用数字键来实现这些键的功能(例如:2,4,5,6,8)。这些游戏事件允许可移植性应用的开发使用低级事件。该API定义了以下抽象键事件集合:UP,DOWN,LEFT,RIGHT,FIRE,GAME_A,GAME_B,GAME_C,andGAME_D.

一个应用可以通过调用
public static int getGameAction(int keyCode);
方法来取得键事件到抽象键事件的映射。

如果应用程序逻辑是基于这个方法的返回值的,那么该应用程序是可移植的并且可以不用考虑小键盘的实际设计而执行。

同样也可以把一个抽象键映射到一个按键上:
public static int getKeyCode(int gameAction);
参数gameAction是P,DOWN, LEFT, RIGHT, FIRE, 等等。在一些设备上,超过一个按键被映射到相同的游戏动作(game action),在这种情况下getKeyCode方法只会返回其中之一。良好的应用程序应该把键码映射到抽象时间上,并依赖于按键动作返回的结果来做决定。

如果游戏在执行过程中,那么按键和抽象键事件的映射关系无法被改变。

下面是一个应用如何用游戏动作来解释按键动作的例子:

class MovingBlocksCanvas extends Canvas {
        public void keyPressed(int keyCode) {
            int action = getGameAction(keyCode);    
            switch (action) {
            case LEFT:
                moveBlockLeft();
                break;
            case RIGHT:
                ...
            }
        }
    }

低级API同样支持指针(触摸笔或手指)事件,但是下面的输入机制可能不会在所有设备上存在,下面回调方法可能永远不会在有限设备里被调用:

public void pointerPressed(int x, int y);
    public void pointerReleased(int x, int y);
    public void pointerDragged(int x, int y);

应用程序可以用Canvas类的如下方法检查指针设备是否可用:

public static boolean hasPointerEvents();
    public static boolean hasPointerMotionEvents();

InterplayofHigh-LevelCommandsandtheLow-LevelAPI

类Canvas它被用来处理低级事件和绘图,是Displayable的子类,应用程序可以给它绑定Command。ThisisusefulforjumpingtoanoptionssetupScreeninthemiddleofagame.另外一个例子是基于地图的导航程序,在这个应用中键被用来移动其中的地图但是Command被用来处理高级动作。

一些设备也许没法在Canvas和低级事件机制正处于使用的时候调用Command。在这种情况下,API实现会提供一个切换到一个Command模式和返回的方式。这个Command模式会在Canvas的内容之上弹出一个menu。此时,Canvas的方法hideNotify()和showNotify()会被单独地调用来指示Canvas什么时候被掩盖和不掩盖。

Canvas像Screen对象一样会有一个title和一个Ticker。Canvas同样有一个全屏模式,此时title和Ticker是不显示的。这种设置模式表明应用程序希望Canvas尽可能多的占用物理显示屏。在这种模式下,API实现会用弹出菜单的方式来重用title。在普通模式(notfull-screen),Canvas的外观跟Screen类的很像,这样就保持了应用切换低级Canvas对象和高级Screen对象时保持可视化连续性(就是看起来差不多,但其实是不同的可是对象了)。

GraphicsandTextinLow-LevelAPI

TheRedrawingScheme

所有Screen都会自动重绘,但是Canvas不会。因此,开发者利用低级API时必须:理解它的重绘模式。

在低级API中,Canvas的重绘是异步进行的,因此作为一个优化方案多个重绘请求可以在一个单独的调用中实现。这意味着应用程序通过调用Canvas类的repaint()方法来请求重绘。实际上绘图是paint()方法完成--它由Canvas的子类提供并不需要同步发生repaint()。也许重绘会玩一会发生,多个重绘请求也会导致一个单独的paint()调用。应用可以用servicRepaints()方法来刷新重绘请求。

作为一个例子,假定一个应用程序把一个宽为wid高为ht的盒子从坐标(x1,y1)移动到坐标(x2,y2),其中x2>x1,y2>y1:

// move coordinates of box
    box.x = x2;
    box.y = y2;
    
    // ensure old region repainted (with background)    
    canvas.repaint(x1,y1, wid, ht);
    
    // make new region repainted
    canvas.repaint(x2,y2, wid, ht);
    
    // make everything really repainted
    canvas.serviceRepaints();

最后一行代码让重绘线程处于执行计划中。重绘线程从事件队列中发现2个请求并且重绘了需要重绘范围的联合区域。

graphics.clipRect(x1,y1, (x2-x1+wid), (y2-y1+ht));      
    canvas.paint(graphics);

在一个实现的这个虚构部分,canvas.paint()调用导致应用程序定义的paint()方法的调用。

DrawingModel

基本绘画操作就是像素置换,它被用于几何变换操作,例如:线和矩形。对于离屏图像(我认为是那些不在当前屏幕显示的图像,它们可能存在于程序的图像缓冲或其它区域),需要全部透明度支持,同时部分透明度(alphablending:α交融)支持是可选的。

提供一个24位(24-bit)的颜色模型,每个颜色的红、绿和蓝部分各是8位。不是所有设备都支持24位色,这些设备会把应用程序里它不支持的颜色的映射到设备上有的颜色。Display类中提供了检测设备特性的工具,例如颜色是否支持和支持多少灰阶。这使应用程序可以针对某个设备作出相应调整而不放弃设备独立性。

图形可以被直接渲染到显示屏或者到一个离屏图像缓冲。经渲染的图像的目的地依赖于原始的图形对象。一个将要渲染到显示屏的图形对象被传递到Canvas对象的paint()。这是唯一能获得那些目的地是显示屏的图形对象的方法。此外,应用程序会用仅仅存在于paint()方法内的这种图形对象来画图。

一个要渲染到一个离屏图像缓冲区的图形对象可以通过调用getGraphics()方法来获得所要的图像。这些图形对象会被所属应用无限期拥有,任何时刻都可以对这些图形对象发起请求。

Graphics类有一个通过setColor()方法设置的当前颜色。所有集合变换,包括线、矩形和弧形,都使用当前颜色。这些图形的setColor()操作用当前颜色像素替换掉目标像素。它们没有背景颜色。任何背景的绘制都通过应用程序使用setColor()和渲染方法调用来明确的完成。

完全透明支持是需要的,部分透明支持是可选的。透明度(完全和部分)存在于从PNG文件加载或ARGB数组数组中加载的离屏图像。图像的这种创建方式是不可变的,原因在于防止应用程序对包含在图形(image)里的像素数据做任何改变。渲染这样定义:任何渲染操作的目的地始终完全地包含全部透明像素(透明像素不止一个,有很多,这里指全部)。

CoordinateSystem

可绘图区域和图片的原点是显示屏左上角的(0,0)。x坐标的数字值从左到右单调递增,y坐标从上到下单调递增。应用程序会假定坐标系统中水平和垂直距离是和实际设备显示屏上的距离是相等的。如果设备中像素的形状和正方形明显有别,UI的API实现会做必要的坐标转换。提供转化该坐标系统的原点的工具。所有坐标都以整数型指定。

坐标系统描述像素之间的位置,而不是像素本身。因此,位于显示屏左上角的正方形通过坐标(0,0),(1,0),(0,1),(1,1)来界定。

应用程序应该通过Canvas类中的如下方法询问可绘区域的情况:

public static final int getWidth();
    public static final int getHeight();

FontSupport

一个应用程序会使用如下指定的字体属性之一。但是API底层实现只会用这些指定字体的一个子集。所以根据具体API实现,会返回一个最接近于程序要求的字体。

该系统中每个字体都是被独立实现的。程序员可以调用staticgetFont()方法而不是生成新的Font对象:这是一个跟字体使用有关的常见来防止创建垃圾的例子。

Font类提供访问字体度量标准的请求方法。在请求一个字体时,下面的属性可能会被用到(都在Font类中):

*  Size: SMALL, MEDIUM, LARGE.
  *  Face: PROPORTIONAL, MONOSPACE, SYSTEM.
  *  Style: PLAIN, BOLD, ITALIC, UNDERLINED.

Concurrency

UIAPI已经被设计成线程安全的。方法都会被Callback、TimerTask和其它由应用创建的线程调用。同时,API实现通常不会持有对于应用来说可见对象的任何锁。Thismeansthattheapplications'threadscansynchronizewiththemselvesandwiththeeventcallbacksbylockinganyobjectaccordingtoasynchronizationpolicydefinedbytheapplication.用Canvase.servicRepaints方法的时候会抛出一个异常。这个方法调用并等待paint方法的完成。严格来讲,servicRepaints不会直接调用paint,但是它会使得另外一个线程来调用paint。在另外一方面,servicRepaints会在paint方法返回之前阻塞。这一点在下面这个例子中更为明显:假定servicRepaints的调用者持有一个锁,但是该锁同时是paint方法需要的,由于paint方法会被其它线程调用,那个线程会因等待该锁而阻塞。因此,这个锁会被servicRepaints的调用者持有,直到paint返回它阻塞,这是一个死锁。为了避免这个死锁,servicRepaints的调用者必定不能持有paint方法调用时所需要的任何锁。

UIAPI同时也包含了跟其它UI工具包类似的机制:它用来用事件流来序列化action(动作、响应)。方法Display.callSerially请求一个Runnable对象的run的执行,并用事件流(eventstream)来序列化。用了servicRepaints()的方法通常可以用callSerially()来重写。下面的代码解释这项技术:

class MyCanvas extends Canvas {    
        void doStuff() {
            // <code fragment 1>    
            serviceRepaints();
            // <code fragment 2>    
        }
    }
 

下面的代码是唯一实现同样功能的方法:

class MyClass extends Canvas 
       implements Runnable {            
        void doStuff() {
            // <code fragment 1>
            callSerially(this);
        }

        // called only after all pending repaints served    
        public void run() {
            // <code fragment 2>;
        }
    }

ImplementationNotes

List或ChoiceGroup的实现会包含键盘快捷方式来聚焦和选择选项元素,但是这些快捷方式对应用程序是不可见的。

在某些UI组件的实现中--Screen和Item--会基于本地组件。当Java对象不再需要时的释放过期资源的操作取决与该实现。一个可行的实现场景是和KVM的垃圾回收器挂钩。

相关推荐