深入理解MIDP低层用户界面API

来源:互联网 发布:浙江省网络图书馆 编辑:程序博客网 时间:2024/04/30 06:16
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 160x600, 创建于 08-4-23MSDN */google_ad_slot = "4367022601";google_ad_width = 160;google_ad_height = 600;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>

  MIDP为更好地控制应用程序用户界面提供了一套低层(low-level)API。但是,希望使用这些特性的应用程序同样继承得到了很多责任和风险。我们需要考虑使用MIDP低层API的优点和缺陷。

  建立用户界面的时候,MIDP提供了很高的效率和灵活性。其效率可以从MIDP调用高层用户界面控件方面看到。但MIDP还提供了一套低层户界面APIMIDP低层户界面提供了更细微的控制,对应用程序用户界面的定制几乎没有限制。但是,我们在使用低层API的时候需要管理其风险,并作出折衷的选择。本文将解释建立低层户界面的相关工作,并讨论你可能遇到的一些麻烦和风险。

  从本质上说,低层户界面由Canvas类和一组支撑类(例如图形、字体和图像)组成。我们在Canvas的实例中使用支撑类来建立不同的视觉效果。

  Canvas是个抽象类。为使用低层API建立用户界面,应用程序必须建立Canvas的一个子类(subclass),并实现paint()方法。paint()方法可能并不是唯一需要实现的方法。例如,Graphics的实例是作为参数传递给paint()的,它提供了设备绘图能力(它允许在象素层面直接与显示设备交互操作)的入口。

  使用MIDP低层类的优点之一是,我们可以使用少量的类建立非常复杂的用户界面。但是,我们要对使用这种方法提出一些告诫。看看下面的例子,它在屏幕上绘出一个字符串:

 

  public void paint(Graphics graphics) {

  graphics.drawString("This is a very boring MIDlet "+

  "that is nearly putting me to sleep!", 0, 0,

  Graphics.TOP|Graphics.LEFT);

  }

  图1显示了该MIDlet在J2ME无线工具包中运行的情况(已经把Canvas设置为当前的显示设备)。尽管这个MIDlet把字符串显示在屏幕上了,但是从表面上就可以看到有少量的问题。

 

 

  图1.重叠:这是使用Canvas输出文本的一个例子。

  由于首先没有清除Canvas,文本直接绘制在已有的图像上。

  第一个问题是在输出那个句子(字符串)之前没有清除Canvas。其结果是该字符串直接绘制在已有的图像上了,而这一幅图像恰好是应用程序管理软件(AMS)的载入屏幕。在下面的例子中,我添加了一行代码来清除屏幕。这种技术是使用相同的颜色象素填充整个屏幕。

 

  public void paint(Graphics graphics) {

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

  graphics.drawString("This is a very boring MIDlet "+

  "that is nearly putting me to sleep!", 0, 0,

  Graphics.TOP|Graphics.LEFT);

  }

 

 

  图2.设置为黑色:我调用Graphics.fillRect()来清除屏幕。

  但是由于调用fillRect()和drawString()时颜色都设置为黑色,文本消失了。

  但是现在又出现了一个新问题。屏幕看起来变成空白的了(图2)。这是由于Canvas使用了相同颜色的象素填充了整个屏幕,而当前的Canvas颜色正好被设置为黑色。于是,在调用drawString()方法的时候,文本也使用黑色绘制,根本没有任何视觉效果。为了得到理想的效果,我不得不处理色彩问题。

  zmbbs=1;

  使用色彩

  我们可以调用setColor()和setGrayScale()来处理色彩和灰度效果。setColor()带有三个参数,它描述了我们想得到的RGB(红,绿,蓝)值。在调用setColor()的时候,需要给每一个RGB颜色维度传递一个0到255之间的值。传递不同的值将引起色彩结果中相应色彩的饱和度更高或更低。例如使用setColor(0, 100, 255)意味着颜色没有红,大约40%的绿,100%的蓝;调用setColor(255, 0, 0)将显示大红。图3显示了与下面的代码对应的颜色条:

  · graphics.setColor(255,255,255);

  · graphics.setColor(0,0,0);

  · graphics.setColor(100,100,100);

  · graphics.setColor(255,0,0);

  · graphics.setColor(0,255,0);

  · graphics.setColor(0,0,255);

 

 

  图3.知道这些颜色吗?颜色条中的颜色值与上面代码中对setColor()不同的调用相对应

  调用setGrayScale()只需要一个参数,它描述了我们想得到的黑色和白色的平衡。灰度范围是0到255,其中0是黑色,255是白色。

  在理解了setColor()是如何工作的之后,现在我可以首先把屏幕清除为白色的背景,接着在白色背景的顶部绘制黑色的文本(图4)。

 

 

  图4.对比:把背景色设为白色,文本的颜色设为黑色意味着这次输出文本的时候用户可以看到。

 

  public void paint(Graphics graphics) {

  graphics.setColor(255,255,255);

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

  graphics.setColor(0,0,0);

  graphics.drawString("This is a very boring MIDlet "+"that is nearly putting me to sleep!", 0, 0,

  Graphics.TOP|Graphics.LEFT);

  }

  现在的情形看起来好多了。通过调用fillRect()清除了AMS图像,把背景色设置为白色,文本现在用黑色显示了。

  但是,我们还需要弥补一些缺陷:字符串对于屏幕来说太长了。Graphics.drawString()方法并不支持换行,因此我必须自己处理这种事务。在上面简单的例子中,我可能侥幸地把句子分成两个部分,软件两次调用drawstring()。但是我仍然需要计算出从什么位置开始绘制下一行。我猜10或15个象素就够了;但是还有更科学的计算方法,就是使用字体量度(font metrics)。

  使用字体量度

  我们可以在Font(字体)类中使用字体量度和字体信息。为了得到当前使用的字体,可以调用Graphics.getFont();可以调用Font.getHeight()得到字体的高度。下面的代码改善了例子并演示了如何使用字体量度把一行分成两个部分。

 

  public void paint(Graphics graphics) {

  String text1 = "This is a very boring MIDlet ";

  String text2 = "that is nearly putting me to sleep!";

  graphics.setColor(255,255,255);

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

  graphics.setColor(0,0,0);

  Font font = graphics.getFont();

  int fontHeight = font.getHeight();

  graphics.drawString(text1, 0, 0,

  Graphics.TOP|Graphics.LEFT);

  graphics.drawString(text2, 0, fontHeight,

  Graphics.TOP|Graphics.LEFT);

  }

 

 

  图5.这就是分行?该方法把文本分成了两个部分,并两次调用drawString()得到分行的效果。

  现在,我的示例变得越来越复杂了,而我所需要做的仅仅是把一行文本输出到屏幕上。尽管Canvas为建立用户界面提供了大量的细微控制和灵活性,但是你已经看到需要添加多少细节信息了。

  当我们为多个设备编写应用程序的时候,管理这些细节信息就更加重要了。在不同的设备之间屏幕的尺寸和支持的颜色是经常改变的。为了确保Canvas能够适应任何环境,你必须非常小心。换句话说,猜测字体高度、换行、图像大小等很可能使你陷入麻烦之中。

  zmbbs=1;

  简单换行

  我一直在考虑可移植性,因此我再次研究这个例子,看一看如何提高移植性,并允许任何字符串正确地输出。

  换行算法可能很复杂。为了保持例子的简单性,换行将在字符层面,而不是在单词层面进行。同时,我没有考虑垂直滚动的问题,这意味着这种实现假定全部文本可以在一个屏幕中输出。

  这种方法检测字符串中的每个字符,每次一个,并根据屏幕上的所有其它字符,决定在何处绘制该字符。绘制每个字符的时候都沿着Y取值(不变),X取值加上该字符的宽度(从而不断增长)。当到达屏幕的右边界时候,如果剩余的空间无法填充下一个字符,那么Y的值就增加(加上字体的高度),并把X设置为0。其结果就是字符层次的换行效果。

  Font类包含了测量给定字体的某个特定字符的大小(尺寸)的方法。当你处理true-type字体的时候,需要知道其字符的宽度是不同的(例如,“W”就比“I”宽)。这就需要使用Font.charWidth()单独测量每个字符。增强了换行的例子如列表1所示。

  列表1:换行的代码

 

  public void paint(Graphics graphics) {

  String text = "This is a very boring MIDlet " +"that is nearly putting me to sleep!";

  //清除屏幕

  graphics.setColor(255,255,255);

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

  //把颜色设置为黑色

  graphics.setColor(0,0,0);

  //得到字体高度

  Font font = graphics.getFont();

  int fontHeight = font.getHeight();

  //把字符串转换为字符数据

  char[] data = new char[text.length()];

  text.getChars(0, text.length(), data, 0);

  int width = getWidth();

  int lineWidth = 0;

  int charWidth = 0;

  int y = 10;

  int x = 0;

  char ch;

  for(int ccnt=0; ccnt < data.length; ccnt++)

  {

  ch = data[ccnt];

  //测量要绘制的字符

  charWidth = font.charWidth(ch);

  lineWidth = lineWidth + charWidth;

  //看是否需要新的行

  if (lineWidth > width)

  {

  y = y + fontHeight;

  lineWidth = 0;

  x = 0;

  }

  //绘制字符

  graphics.drawChar(ch, x, y,Graphics.TOP|Graphics.LEFT);

  x = lineWidth;

  }

  }

  这个简单的,甚至于乏味的例子变得越来越有趣了。它演示了低层API提供了大量的细微控制。但是,它同时突出了为了执行看起来简单的事务(例如文本换行)所发生的复杂性。这样的实现应该引发作者考虑使用低层API的优点,以确保这些优点大于实现和维护代码复杂性的代价。

  到此为止,我演示了在屏幕上绘制文本的基础知识。但是,Graphics类还定义了绘制形状和图像的方法。使用这些方法的原则在本质上与例子演示的是一样的。因此,现在我将调整方向,讨论Canvas了的事件处理能力的一些情况。

  低层事件处理

  低层UI(用户界面)提供了大量的事件处理选项。为了映射软键(soft-keys,键盘上的物理键,它映射为显示屏上的标签),你可以给Canvas添加命令,就像高层UI组件(类似List和Form)一样。但是Canvas也允许捕捉设备上可以使用的大多数键的未处理按键事件。监视按键活动的Canvas API如下:

 

  事件

  描述

  keypressed()

  按下键盘上的物理键的时候调用这个事件。

  keyreleased()

  放开某个键的时候调用这个事件。

  keyrepeated()

  压住某个键的时候调用这个事件。并不是所有的平台都支持这个事件。你可以调用hasrepeatevents()验证是否支持它。

  为了监视按键事件(key events),Canvas的子类必须重载其中一个方法。每个键与一个整型编码对应,作为参数传递到事件中。我们要注意,在不同的平台之间键的编码很可能会不同,使得键事件处理成为低层UI移植性的另一个敏感区域。例如,在设备A上,按下键盘上的“2”可能通过编码25得到,但是在设备B上,相同的键可能通过编码12得到。幸运的是,MIDP提供了规范化按键事件的工具。

  zmbbs=1;

  跨设备的按键处理

  由于键编码在多个设备上可能不同,MIDP提供了键映射,这样设备实现就能把未处理的键事件映射为已知的常数值。其中最简单的形式就是Canvas类中的数字键映射。现在有10个常数值与10个数字键盘对应,另外加上了“*”和“#”键。因此,如果某个应用程序需要检测什么时候按下了“6”,可以采用下面的方式:

 

  public void keyPressed(int code)

  {

  if (code == KEY_NUM6)

  {

  System.out.println("Key 6 Pressed!");

  }

  }

  现在的问题是,如果某个设备支持多于10个数字的键盘(例如数字和字符键盘),MIDP没有为所有键的映射提供常量。如果某个应用程序需要监视扩充的键编码范围,就需要特定设备的键处理部分。

  作为与数据相关的键的补充,MIDP提供了对于很多更标准的控制键(例如上、下、左、右等等)的映射支持。你可以在某个按键处理事件内部使用Canvas.getGameAction()方法把未处理的键编码转换为Canvas类定义的常数值:UP、DOWN、LEFT、RIGHT、FIRE、GAME_A、GAME_B、GAME_C、GAME_D。在某些情况下,这种映射工作得非常顺利。很多设备拥有一些滚动操作类型,它们被映射为上、下、左和右键。但是,FIRE或通用GAME_B键的某种方式的映射对于特定应用程序来说可能不是最佳的。

  下面的代码演示了keyPressed()事件如何转换FIRE和GAME_B键的,对于J2ME无线工具包来说,它们被对应地映射成万能按键(jog dial)中间的“select”和“3”键。下表显示了游戏键常量、实际键(在无线工具包模拟器中)和未处理的键编码是如何彼此对应的:

 

  游戏常量

  物理键 (j2me无线工具包)

  键编码值

  fire

  "select" 键

  -5

  game_b

  "3" 键

  51

  下面的例子演示了在按键处理事件中是如何使用getGameAction()方法作出决定的:

 

  public void keyPressed(int code)

  {

  int action = getGameAction(code);

  switch (action)

  {

  case FIRE:

  {

  System.out.println("FIRE Pressed!");

  break;

  }

  case GAME_B:

  {

  System.out.println("GAME_B Pressed!");

  break;

  }

  }

  }

  尽管低层API的实现可能具有挑战性,但是它们为开发者提供了解决移植性难题的一条途径,而这是采用其它的方法很难或者不可能实现的。在考虑周全、注意细节的情形下,低层API可以用于建立外貌和感觉独特的定制用户界面,以适合特定应用程序的需求。这种灵活性不以可移植性为代价。通过使用字体量度,Canvas屏幕度量方法和键映射,MIDP应用程序可以利用低层API的优势,同时保留跨设备的完全可移植性。

 

<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 728x15, 创建于 08-4-23MSDN */google_ad_slot = "3624277373";google_ad_width = 728;google_ad_height = 15;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
<script type="text/javascript"><!--google_ad_client = "pub-2947489232296736";/* 160x600, 创建于 08-4-23MSDN */google_ad_slot = "4367022601";google_ad_width = 160;google_ad_height = 600;//--></script><script type="text/javascript"src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>