BlackBerry 应用程序开发者指南 第一卷:基础--第3章 创建用户接口(UI)
作者:Confach 发表于April 23,2006 15:02 pm
版权信息:可以任意转载, 转载时请务必以超链接形式标明文章原始出处 和作者信息.
http://www.cnblogs.com/confach/articles/382744.html
3
第3章 创建用户接口(UI)
UI API 显示UI组件。 管理UI组件 创建客户定制的UI组件 操作图片 使用图像对象画图 监听UI对象的改变 |
UI API
当你为BlackBerry设备编写应用程序时,使用下面2组UI API的一组:
- MIDP UI API (javax.microedition.lcdui包)
- BlackBerry UI API (net.rim.device.api.ui包)
如果你正在编写一个在任何MIDP兼容设备上运行的应用程序,请使用MIDP UI API.如果你正在编写专门运行在BlackBerry设备上的应用程序,那就使用BlackBerry UI API吧。BlackBerry API提供了访问BlackBerry设备的特定特性的功能,并且也允许更成熟的UI布局(layout)和交互。
显示UI组件
显示屏幕(Screen)
UI 的主要结构是Screen。一个应用程序一次只能显示一个屏幕。
显示栈(Stack)
Screen对象在一个一组有序的Screen显示栈里得到维护。在栈顶的Screen对象是显示给用户的活动Screen。当应用程序显示一个Screen时,它将这个Screen压入到栈顶。当关闭一个Screen,将这个Screen从栈里移出,然后显示栈里的下一个Screen,如果必要会重绘它。
注:每个Screen在栈里只出现一次。如果同一个Screen压入到栈不止一次,VM会抛出一个运行时异常。当用户完成和Screen交互,应用程序必须将Screen从栈里移出,以致内存不必再用。不要在同一时间里使用多个Screen,因为每个Screen使用独立的线程。
Screen的类型
在多数情况下,创建一个Screen最有效的方法是创建一个扩展Screen或其任一子类,FullScreen或MainScreen的类。
类 | 描述 |
Screen | 使用Screen类定义一个管理器布局Screen上的UI组件,并且使用在超类Field定义的常数的样式(Style)定义一明确的Screen。 |
FullScreen | 缺省的,一个FullScreen包含单个垂直[1]的域管理器(Field Manager)。使用一个FullScreen提供了一个空的Screen,在这个空的Screen上,你可以增加UI组件到这个标准的垂直布局里。如果需要另外类型的布局,例如水平的或对角的,使用一个Screen类,并且在里面增加一个管理器。 |
MainScreen | MainScreen类提供常见的标准BlackBerry应用程序常见特性。对你的应用程序的第一个Screen,使用一个MainScreen对象来保持和其他BlackBerry应用程序的统一。MainScreen提供一下的UI组件: l Screen标题的缺省位置,标题后的一个SeperatorField l 一个包含在VerticalManager里的滚动的主界面。 l 有一个Close菜单项的菜单。 l 当用户点击Close菜单项或者按Escape键时缺省的关闭操作。 |
响应用户交互
BlackBerry API提供一个和Java标准版本类似的事件监听框架。特殊的,2个监听接口使程序接收和响应用户交互:TrackWheelListener和KeyboardListnener。Screen类和其子类都实现了这些方法。
提供screen导航(navigation)
BlackBerry应用程序为用户提供一个菜单来完成操作。避免使用按钮(Button)或其他占据Screen空间的UI组件。
注:按滑轮访问菜单。
当创建一个FullScreen或Screen,在构造子里指明DEFAULT_MENU和DEFAULT_CLOSE参数来提供缺省的导航。
FullScreen fullScreen = new FullScreen(DEFAULT_MENU | DEFAULT_CLOSE);
参数 | 描述 |
DEFAULT_MENU | 这个参数增加一个缺省的菜单,它包含了不同的菜单项,这依赖域用户的上下文环境。例如,如果一个EditField获得焦点,将显示Cut,Copy和Paste菜单项。所有已选择的域提供Select和Cancel Selection菜单项。 |
DEFAULT_CLOSE | 这个参数增加一个缺省行为的Close菜单项到菜单,当用户点击Close菜单项或者按Escapes按钮,如果Screen上的任何东西改变,一个确认的对话框将会出现。如果这个Screen是栈里的唯一一个Screen,应用程序将关闭。 |
当创建一个MainScreen时,缺省的导航会自动提供。
增加菜单项
创建MenuItem对象。
private MenuItem viewItem = new MenuItem("View Message", 100, 10) { public void run() { Dialog.inform("This is today’s message"); } }; |
MenuItem构造子接受下面的3个参数:
参数 | 描述 |
text | 菜单项的名称 |
ordinal | 菜单项的顺序;一个越大的值表明了这个菜单项越靠近菜单的底部。 |
priority | 接收缺省焦点的菜单项优先级 |
run()定义了当用户点击菜单项发生的操作的实现。如果你没有使用本地资源,重写toString()方法来指定菜单项的名字。
为了在应用程序加入上下文菜单给field,调用getLeafFieldWithFocus(),并且调用getContextMenu(),其返回值决定哪一个Field接收makeMenu()里的客户化菜单项。为了得到更多信息,参看60页的“创建客户化的上下文菜单”。
当增加你自己的菜单项时,显式的定义一个Close菜单项。
为了增加菜单项到Screen里,重写Screen.makeMenu()方法:
protected void makeMenu(Menu menu, int instance) { menu.add(viewItem); menu.add(closeItem); } |
如果你扩展Screen或其任一子类,那么当用户点击滑轮时,缺省的TrackwheelListener实现调用makeMenu()。
如果你没有扩展Screen,那么实现TrackwheelListener。特殊地,trackwheelClick()的实现创建一个新的菜单,增加菜单项以及在Screen上显示菜单。
public boolean trackwheelClick(int status, int time) { Menu appMenu = new Menu(); makeMenu(appMenu, 0); // Add menu items. appMenu.show(); // Display the menu on screen. return true; } |
注:为了创建菜单项提供附加的功能,请扩展MenuItem类。为了得到更多信息,参看60页“创建客户化的上下文菜单”。
显示对话框
PopupScreen类通过使用它的子类,Dialog和Status,来提供创建对话框和状态Screen的特性。Popup screen不会压入到显示栈中,为了显示一个popup screen,调用Dialog.ask(int)或Status.show().
为了控制对话框的布局,使用DialogFieldManager对象,为了得到更多的信息,参看50页的“为一个PopupScreeen指定布局”。
为了显示一个对话框,使用下面的一个参数来调用Dialog.ask():
参数 | 描述 |
D_OK | 显示一个字符串,并且提示用户点击OK。 |
D_SAVE | 实现一个字符串,并且提示用户点击Save,Discard,或者Cancel;按Escape取消。 |
D_DELETE | 显示一个字符串,并且提示用户点击Delete或者Cancel;按Escape撤销。 |
D_YES_NO | 显示一个字符串,并且提示用户点击Yes或No。 |
int response = Dialog.ask(Dialog.D_SAVE); if (Dialog.SAVE == response || Dialog.CANCEL == response) return false; if ( Dialog.DISCARD == response ) _item.deleteItem(_itemIndex); |
为了指定一个对话框的缺省的响应,使用一个接受defaultChoice作为参数的Dialog.ask()版本。
int response = Dialog.ask(Dialog.D_YES_NO, "Are you sure?", Dialog.NO); |
显示状态消息
调用Status.Show()显示一个状态消息。缺省的,状态屏幕保留其屏幕 2秒钟。
Status.show("Status screen message");
参看API参考获取Status.Show()的版本信息,它使你可以指定额外的参数,例如不同的图标或者保持状态对话框可见的时间长短。你可以创建模态的状态对话框(需要用户取消它们),也可以创建计时的状态对话框(在指定的时间后自动取消)。
显示域(Field)
所有UI组件以包含在管理器里的成矩形的field的形式表现。Field的大小取决于它的布局需求。管理器为它们包含的field提供滚动(条)。
BlackBerry JDE在net.rim.device.api.ui.component包里提供一个预创建接口控件和组件的库。多数情况下,你可以使用这些对象构建UI应用程序。
为了创建指定的field控件(如包含多个元素的文本field),扩展Field类或者其任意子类来创建你自己定制的类型。为得到更多信息,参看53页的“创建定制的field”。
注:参看API参考获取更多关于指定field类的有效、支持的格式的信息。如果使用一个不支持的格式实例化一个Field,将抛出一个IllegalArgumentException异常。
Bitmap Field
一个BitmapField包含了位图。当使用Graphics对象绘图时使用BitmapField。为了修改一个field的内容,调用BitmapField的绘图方法。为得到更多信息,参看73页的“使用graphics对象绘图”.
Bitmap myBitmap = Bitmap.getPredefinedBitmap(Bitmap.INFORMATION); BitmapField myBitmapField = new BitmapField(myBitmap.getPredefinedBitmap(myBitmap)); ... mainScreen.add(myBitmapField); |
有4种预定义的位图:
- Bitmap.INFORMATION
- Bitmap.QUESTION
- Bitmap.EXCLAMATION
- Bitmap.HOURGLASS
为了使用原始的.gif或.png作为位图,调用getBitmapResource().
注:一个二进制资源的大小,如一个.png文件,不能超过63,000字节。
private static final Bitmap myBitmap = Bitmap.getBitmapResource("customBitmap.gif"); ... BitmapField bitmapField = new BitmapField(myBitmap); mainScreen.add(bitmapField); |
Button Field
ButtonField包含了用户选择来完成操作的按钮。使用ButtonField可以创建超出菜单的扩展交互的界面。
ButtonField mySubmitButton = new ButtonField("Submit"); ButtonField myResetButton = new ButtonField("Reset"); mainScreen.add(mySubmitButton); mainScreen.add(myResetButton); |
为了给button增加功能,扩展ButtonField并且覆写trackwheelClick()方法,以让它能完成一个操作来代替调用菜单。当用户点击button后为了接受消息,使用一个FieldChangeListener对象。为得到更多信息,参看78页的“监听UI对象的改变”.
Choice Field
Choice field类似于下拉列表。这里有2种choice field:包含整数的和包含可以转化为字符串的对象。
你也可以显示一组选项作为check box或者radio button。为了得到更多信息,参看45页的“option field”。
为了从ChoiceField里选择一个值,用户可以完成下面的操作:
- 点击field,并且按Space键。
- 按住Alt键,滚动滑轮。
- 打开菜单,选择Change Option。
类 | 描述 |
NumericChoiceField | NumericChoiceField是一个包含了一组数字的ChoiceField。NumericChoiceField实例典型的用在较少的数字范围内(到20个之多)。 NumericChoiceField myNumericChoice = new NumericChoiceField( "Select a number: ", 1, 20, 10); mainScreen.add(myNumericChoice); 注:对于大数量的数字,使用GaugeField,为得到更多信息,参看47页的“Gauge Field)。 |
ObjectChoiceField | ObjectChoiceField是一个包含了对象的ChoiceField。这个Field的所有对象必须实现Object.toStirng()以提供他们自己的字符串表现形式。 |
Option Field
OptionField允许用户从列表种选择条目。为允许用户从选择列表中选择多个条目,使用CheckBoxField。为允许用户从选择列表中仅选择一个条目,使用RadioButtonField。
类 | 描述 |
CheckBoxField | 每个CheckBoxField是一个独立的对象,与其他的可选框无关。 CheckboxField myCheckbox = new CheckboxField("First checkbox", true); CheckboxField myCheckbox2 = new CheckboxField("Second checkbox", false); ... mainScreen.add(myCheckbox); mainScreen.add(myCheckbox2); |
RadioButtonField | 多个RadioButtonField对象组合在一个RadioButtonGroup中,这样用户一次只能选择一个选项。 RadioButtonGroup rbGroup = new RadioButtonGroup(); RadioButtonField rbField = new RadioButtonField("First field"); RadioButtonField rbField2 = new RadioButtonField("Second field"); ... rbGroup.add(rbField); rbGroup.add(rbField2); ... mainScreen.add(rbField); mainScreen.add(rbField2); |
Date Field
在你的应用程序中,一个DateField显示当前的日期和时间。
类型 | 描述 |
DATE | 显示年月日 |
DATE_TIME | 显示年月日,时分秒 |
TIME | 显示时分秒 |
当创建一个DateField时,调用System.currentTimeMillis()得到当前时间。
DateField dateField = new DateField("Date: ", System.currentTimeMillis(), DateField.DATE_TIME); mainScreen.add(dateField); |
Date Field缺省为可编辑的。为了创建一个用户不能编辑的Date Field,在其构造子中指定Field.READONLY参数。
将为可编辑的Date Field提供一个缺省的Change Options菜单项。
Edit Field
一个EditField允许用户在此Field里输入文本。AutoTextEditField, EditField, 和 PasswordEditField 都扩展了BasicEditField.
注:net.rim.device.api.ui.component.TextField类,扩展了Field类,并且是抽象的。实例化它的子类,例如RichTextField 或 EditField,就是创建一个显示文本或允许用户输入文本的UI Field。
你可以应用下面的过滤项(filter)到Edit Field中。
过滤项 | 描述 |
DEFAULT_MAXCHARS | 限制field中字符的个数。对于Edit Field,字符的最大个数缺省为15。 |
FILTER_DEFAULT | 这个是缺省的文本输入过滤项。当构造子需要此过滤项,但是你又不想应用任何特定的过滤项时,那么使用它。 |
FILTER_EMAIL | 仅允许有效的internet消息地址字符(例如,用户可以仅输入一个@标记).它会自动将文本格式化为internet消息地址格式(例如,当用户第一次按Space键时,一个@符号会出现,用户接着按Space键,每次.’s也随之出现。 |
FILTER_HEXADECIMAL | 仅允许数字和A到F的字母。 |
FILTER_INTEGER | 仅允许数字和负号“-“。 |
FILTER_LOWERCASE | 将字符转化为小写 |
FILTER_NUMERIC | 仅允许输入数字 |
FILTER_PHONE | 仅允许输入有效电话号码字符,数字,连接号(-),加号和减号,左括号和右括号,以及”x”. |
FILTER_PIN_ADDRESS | 仅接受在PIN地址上输入的有效字符。 |
FILTER_UPPERCASE | 将字母转化为大写。 |
FILTER_URL | 仅允许有效的URL字符。它也自动格式化field。(当用户按Space键时它将插入一个节点). |
JUMP_FOCUS_AT_END | 改变field的行为,以致当field获得焦点,并且用户试图滚动时,焦点移动到field的末端(代替移动到下一个field)。 |
NO_NEWLINE | 忽略文本中的换行和回车,例如用户从其他地方拷贝和粘贴的文本。 |
类 | 描述 |
RichTextField | RichTextField类创建一个只读的Field,它可以体格式化为各种不同的字体以及样式,RichTextField虽然不可以编辑,但是可以获取焦点。 mainScreen.add(new RichTextField("RichTextField")); |
BasicEditField | BasicEditField是EditField, 和 PasswordEditField的基类。 BasicEditField是一个可编辑的文本Field,它没有缺省的格式,但是却可以接受过滤。 BasicEditField bf = new BasicEditField( "BasicEditField: ", "", 10, EditField.FILTER_UPPERCASE); mainScreen.add(bf); |
EditField | EditField是一个可以编辑的文本Field,它扩展了BasicEditField。EditField允许用户当问特殊的字符。例如,用户按住A键,并且滚动滑轮来选择各种样式的A字符以及Æ字符。EditField类接受样式,但是有些样式会让EditField失去其功能(例如EditField.FILTER_PHONE). mainScreen.add(new EditField("EditField: ", "", 10, EditField.FILTER_DEFAULT)); |
PasswordEditField | PasswordEditField扩展了BasicEditField,提供下面的功能: l 让用户输入显示为星号(*)。 l 自动文本(AutoText)(或其他自动格式化)不会应用。 l 剪切或拷贝不支持 下面的样例使用了一个构造子,允许你为PasswordEditField提供了一个缺省的初始化值。 |
AutoTextEditField | AutoTextEditField应用了AutoText引擎指定的格式。在本Field中输入的任何文本都会根据BlackBerry设备上的AutoText数据库说明来进行格式化。 某些过滤让一些AutoText输入没有效果。例如,FILTTER_LOWERCASE render一个包含大写无效的AutoText输入。 mainScreen.add(new AutoTextEditField("AutoTextEditField: ", "")); |
Gauge Field
Gauge允许你创建数值的可视表现。GaugeField显示一个进度条或允许用户选择数字。你可以使用一个Label作为它的前缀,并显示gauge的当前值。例如,组合一个GaugeField和一个NumericChoiceField来创建一个用户制作的数字选择的图形化表现。
为了创建一个交互的GaugeField,使用Field.FOCUSABLE和Field.EDITABLE样式实例化field。
GaugeField staticGauge = new GaugeField("1: ", 1, 100, 20, GaugeField.NO_TEXT); GaugeField percentGauge = new GaugeField("Percent: ", 1, 100, 29, GaugeField.PERCENT) GaugeField interactiveGauge = new GaugeField("Gauge: ", 1, 100, 60, Field.FOCUSABLE | Field.EDITABLE); ... mainScreen.add(staticGauge); mainScreen.add(percentGauge); mainScreen.add(interactiveGauge); |
Label(标签)和Separator(分隔)Field
一个LabelField允许你增加文本标签到屏幕中。LabelField是可读的。缺省的,它不能获得焦点。大部分应用程序在它们的第一个屏幕上使用LabelField来显示一个静态的标题。
一个SeparatorField是一个静态的水平线,它跨越屏幕的宽度。使用SeparatorField将屏幕上的相关内容和菜单分组。
MainScreen缺省的在标题后显示一个分割线。
LabelField title = new LabelField( "UI Component Sample", LabelField.ELLIPSIS)); mainScreen.setTitle(title) |
List Field
List允许你创建子项的目录,通过此用户可以滚动并选择单个或多个条目。BlackBerry地址簿就是List对象的一个例子。
你不可以直接将内容加入到field条目中。你的ListField的ListFieldCallback和TreeField的TreeFieldCallback的实现会绘图field。
类 | 描述 |
ListField | ListField包含了数行可选条目。为了显示ListField的内容,为了列表设置ListFieldCallback。为了得到更多信息,参看66页的“创建一个回调对象”. String fieldOne = new String("Mark Guo"); String fieldTwo = new String("Amy Krul"); ... ListField myList = new ListField(); ListCallback myCallback = new ListCallback(); myList.setCallback(myCallback); myCallback.add(myList, fieldOne); myCallback.add(myList, fieldTwo); ... mainScreen.add(myList); 注:为了使用户选择列表中的多个条目,指定ListField作为MULTI_SELECT.ListFieldCallback.add()加入列表元素到向量中,并且调用List.insert()决定适当的位置。 |
ObjectListField | ObjectListField是一个包含以对象作为子项的list field。所有包含在list里的对象必须实现Object.toString(),以提供他们自己的字符串表现。在接口上,ObjectListField以和一个标准的ListField一样提供。 |
Tree Field
TreeField包含父节点和子节点,并且显示一个折叠夹或它们(例如文档或信息折叠夹)之间的树关系。所有节点都是缺省可见的。为了指明一个折叠夹是否可以折叠,调用TreeField 对象的setExpand()方法。
图标显示在包含有子节点的每个节点边上以明确节点是打开的还是折叠的。
String fieldOne = new String("Main folder"); ... TreeCallback myCallback = new TreeCallback(); TreeField myTree = new TreeField(myCallback, Field.FOCUSABLE); int node1 = myTree.addChildNode(0, fieldOne); int node2 = myTree.addChildNode(0, fieldTwo); int node3 = myTree.addChildNode(node2, fieldThree); int node4 = myTree.addChildNode(node3, fieldFour); ... int node10 = myTree.addChildNode(node1, fieldTen); myTree.setExpanded(node4, false); ... mainScreen.add(myTree); |
TreeFieldCallback的实现加入field到树中。为了获得更多关于回调的信息,参看66页的“创建一个回调对象”.
private class TreeCallback implements TreeFieldCallback { public void drawTreeItem(TreeField _tree, Graphics g, int node, int y, int width, int indent) { String text = (String)_tree.getCookie(node); g.drawText(text, indent, y); } } |
管理UI组件
管理布局
使用BlackBerry API布局管理器来安排屏幕上的组件。
下面四个类扩展了Manager类,以提供预定义的布局管理器:
- VerticalFieldManager
- HorizontalFieldManager
- FlowFieldManager
- DialogFieldManager
MainScreen和FullScreen缺省的都使用了一个VerticalFieldManager。仅为这些类定义一个布局管理器实例提供了不同的布局。
注:为了创建一个定制的布局管理器,请扩展Manager。为了得到更多信息,参看63页的“创建定制的布局管理器”。
为一个指定的Screen实例定义布局管理器,完成下面的操作:
- 实例化合适的Manager子类。
- 加入UI组件到布局管理器中。
- 加入布局管理器到屏幕中。
VerticalFieldManager vfm = new VerticalFieldManager(Manager.VERTICAL_SCROLL); vfm.add(bitmapField); vfm.add(bitmapField2); ... mainScreen.add(vfm) |
Manager类定义了多个系统样式的常数,这些系统样式定义了如滚动和对齐的行为。当创建布局管理器时,使用这些样式作为参数。为得到更多信息,参看API参考的net.rim.device.api.ui.Manager。
垂直组织field
VerticalFieldManager垂直地组织field。所有field在一新地线(line)上开始。为了可以垂直滚动,提供Manager.VERTICAL_SCROLL参数。
VerticalFieldManager vfm = new VerticalFieldManager(Manager.VERTICAL_SCROLL); vfManager.add(bitmapField); vfManager.add(bitmapField2); ... mainScreen.add(vfManager); |
缺省地,BitmapField对象在VerticalFieldManager中是左对齐的。
水平组织field
HorizonFieldManager水平组织field。为了可以水平滚动,提供Manager.HORIZONTAL_SCROLL样式。如果没有包含HORIZONTAL_SCROLL参数,field水平排列他们自己,可能会超出屏幕宽度,但是用户不能滚动到超出屏幕右边的内容。
BlackBerry设备没有显示水平滚动指示器或滚动条。
HorizontalFieldManager hfm = new HorizontalFieldManager(Manager.HORIZONTAL_SCROLL); |
水平垂直组织Field
FlowFieldManager先水平组织field,然后再垂直组织。先水平组织Field,直到没有足够空间放另外一个field,然后管理器在下一行上水平的安排它们。首页屏幕(Home Screen)就是一个FlowFieldManager的例子。
FlowFieldManager flManager = new FlowFieldManager(Manager.FIELD_HCENTER); |
指定一个PopupScreen的布局
DialogFieldManager指定了PopupScreen对象的布局。它管理了一个图标,一个消息,以及一列定制的field的布局。图标和消息相互靠近的出现在布局上方,定制的field出现在消息的下方。这个布局是PopupScreen对象的标准布局。为了创建定制的对话框,扩展DialogFieldManager。
BitmapField bitmapField = new BitmapField(Bitmap.getBitmapResource("x.gif")); RichTextField message = new RichTextField("Dialog manager message", Field.NON_FOCUSABLE); LabelField dialogChoice = new LabelField("Choice one", Field.FOCUSABLE); ... DialogFieldManager dialogManager = new DialogFieldManager(); dialogManager.setMessage(message); dialogManager.setIcon(bitmapField); dialogManager.addCustomField(dialogChoice); |
管理UI交互
一个时间只有一个线程(通常是事件调配线程)可以得到UI的访问权。通过下列方式,背后(Background)线程也可从主事件处理或UI绘制代码的外部访问UI:
- 获取并保持事件锁。
- 使用invokeLater()或invokeAndWait()在事件调配线程上运行。
获取并保持事件锁
当它处理一个消息时,事件调配者在事件线程上设置一个事件锁。在没有打断事件调配者处理的情况下,背后线程(也就是,非事件调配线程)在短事件内通过获取这个锁可以访问UI。
为了得到事件锁,调用Application.getEventLock()。和这个对象同步,序列化访问UI。在短期内保持这个锁,因为锁会暂停事件调配者。一个应用程序应该永远不要在EventLock对象上调用notify()或wait()。
class MyTimerTask extends TimerTask { public void run() { synchronized(Application.getEventLock()) { _label.setText("new text " + System.currentTimeMillis()); } } } |
在事件调配线程上运行
如果保持事件锁不合适,创建一个实现Runnable接口的类。在事件调配者上通过下面的3种方法之一调用它的run()方法:
- 调用invokeAndWait(Runnable),以致在事件调配线程上立即调用run()。这个调用会阻塞直到run()完成为止。
- 调用invokeLater(Runnable),以致在所有等候的事件处理后,在事件调配线程上调用run()。
- 调用invokeLater(Runnable,long,boolean)以致在某一指定时间后,事件调配线程上调用run()。在这里,在将Runnable加入到事件队列之前,时间指定了等待时间的长短。如果repeat为true,每隔time毫秒后,Runnable加入到事件队列中,
管理前台事件
系统调用Application.activate()将应用程序带到前台。
大多数的应用程序不需要重写activate()。应用程序应该完成应用程序构造子的任何初始化,包括任何必需的UiApplicaiton.pushScreen()调用.因为对同一个应用程序,activate()能够调用多次,因此在在这个方法中,应用程序不应该完成一次初始化。
当带到前台时,应用程序可以覆写activate()方法完成其他的附加处理。如果覆写了activate(),在方法的定义里调用super.activate(),以致应用程序能正确得重绘。
管理绘图区域
使用XYRect对象
Graphics对象代表了应用程序可用的整个绘图表面。为了界定这个区域,将它分为多个XYRect对象。XYRect在图形上下文(graphics context)的顶端创建一个矩形区域、一个XYRect对象有2个XYPoint对象组成。第一个XYPoint对象代表了XYRect左上方的坐标,第二个XYPoint对象代表了右下方的坐标。每个XYPoint代表了一个由X,Y坐标构成的屏幕的坐标。
XYPoint topLeft = new XYPoint(10, 10); XYPoint bottomRight = new XYPoint(50, 50); XYRect rectangle = new XYRect(topLeft, bottomRight); |
Rectangle对象将XYRect对象的上下文绘制区域界定为(10,10)与(50,50)之间的区域。
为了开始对XYRect对象进行绘图调用,调用pushContext()或pushRegion();
当开始用pushContext()进行绘图调用时,指定区域原点不要调整绘图偏移(Drawing offset)。
graphics.pushContext(rectangle, 0, 0); graphics.fillRect(10, 10, 30, 30); graphics.drawRect(15, 15, 30, 30); graphics.popContext(); |
当你首先调用pushRegion()来调用绘图方法时,区域源(Region Origin)需调整绘图偏移,左上方的XYPoint对象代表了区域源。所有绘图都通过这个数来偏移。
在下面的例子中,pushContext()将XYRect对象的10个象素位放到右边,10个放在下方。
区域源调整了绘图偏移(XYPoint topLeft = new XYPoint(10, 10)).
graphics.pushRegion(rectangle); graphics.fillRect(10, 10, 30, 30); graphics.drawRect(15, 15, 30, 30); graphics.popRegion(); |
旋转(Invert)个区域
旋转一个Graphics对象上的一个区域,它保留像素,只是转化像素值的位(也就是0变为1,1变为0)。大多数field使用旋转来表示焦点,尽管这样,你可以为定制的field创建你自己的焦点行为。
为了旋转Graphics对象的任何一个区域,提供坐标或者旋转一个指定的XYRect对象。指定Graphics对象的一个区域,并且压入栈中。在调用pushContext()或pushRegion()后,提供Graphics对象的一个区域来旋转。
graphics.pushContext(rectangle); graphics.invert(rectangle); // invert the entire XYRect object graphics.popContext(); |
转化(Translate)一个区域
为了将一个Graphics上下文上的区域移动到另外一个地方。调用invoke()。
XYRect rectangle = new XYRect(1, 1, 100, 100); XYPoint newLocation = new XYPoint(20, 20); rectangle.translate(newLocation); |
XYRect将点(1,1)转化为(20,20,)。转化后,XYRect的底部扩展了过去图形上下文的范围,并且重合了。
创建客户定制的UI组件
你仅能将定制的上下文菜单项和布局增加到一个定制的field中。
创建定制的field
为覆写field的缺省行为,创建一个定制的field。
注:不要使用Screen对象来输入文本。Screen对象没有明确的实现此功能,它需要复杂的输入方法,例如国际化的键盘和7100系列的设备。为了实现不同输入方法的无缝集成,扩展Field或者其任一子类。参看53页“创建定制的域”得到更多信息。
DrawStyle接口的实现允许在定制的field上绘制样式。为获得更多信息,参看73页的“创建一个与标准BlackBerry UI一致的接口”。
客户定制的field应该实现所有相关的系统样式。例如,USE_ALL_WIDTH和USE_ALL_HEIGHT适用于许多field。
扩展Field类
扩展Field类和任一其子类,指定定制Field的特征。
public class CustomButtonField extends Field implements DrawStyle { public static final int RECTANGLE = 1; public static final int TRIANGLE = 2; public static final int OCTAGON = 3; private String _label; private int _shape; private Font _font; private int _labelHeight; private int _labelWidth; } |
定义按钮的标签,图形,以及样式
你的构造子的实现定义了按钮的标签,图形,以及样式。
public CustomButtonField(String label) { this(label, RECTANGLE, 0); } public CustomButtonField(String label, int shape) { this(label, shape, 0); } public CustomButtonField(String label, long style) { this(label, RECTANGLE, style); } public CustomButtonField(String label, int shape, long style) { super(style); _label = label; _shape = shape; _font = getFont(); _labelHeight = _font.getHeight(); _labelWidth = font.getWidth(); } |
指定field中对象的安排
任何扩展Field的类必须实现layout().Field管理器调用了layout()方法来决定field应该如何根据可用的控件安排它的内容。
protected void layout(int width, int height) { _font = getFont(); _labelHeight = _font.getHeight(); _labelWidth = _font.getAdvance(_label); width = Math.min( width, getPreferredWidth() ); height = Math.min( height, getPreferredHeight() ); setExtent( width, height ); |
定义需要的宽度
注:在大多数情况下,通过覆写getPreferredWidth(),确保合适的布局出现在定制的布局管理器里。
getPreferredWidth()的实现计算出定制Field的宽度,这个定制Field是基于标签Field的相对尺寸的。使用相对尺寸来确保标签不会超出标签的尺寸。
public int getPreferredWidth() { switch(_shape) { case TRIANGLE: if (_labelWidth < _labelHeight) { return _labelHeight << 2; } else { return _labelWidth << 1; } case OCTAGON: if (_labelWidth < _labelHeight) { return _labelHeight + 4; } else { return _labelWidth + 8; } case RECTANGLE: default: return _labelWidth + 8; } } |
定义需要的高度
注:在大多数情况下,通过覆写getPreferredHeight(),确保合适的布局出现在定制的布局管理器里。
getPreferredHeight()的实现计算出定制Field的高度,这个定制Field是基于标签Field的相对尺寸的。它确保了标签不会超出field的尺寸。
public int getPreferredHeight() { switch(_shape){ case TRIANGLE: if (_labelWidth < _labelHeight){ return _labelHeight << 1; } else { return _labelWidth; } case RECTANGLE: return _labelHeight + 4; case OCTAGON: return getPreferredWidth(); } return 0; } |
定义定制field的外观
paint()的实现定义了BlackBerry设备屏幕上的定制Field的外观,不管什么时候Field的域标记为无效,Field管理器都调用paint()来重绘Field。
技巧:验证paint()是否是有效率的,因为不管什么时候field发生变化,UI框架调用paint()方法。对于大数量的field,使用Graphics.getClippingRect()并在可见的区域里绘图来保存绘制时间。
protected void paint(Graphics graphics) { int textX, textY, textWidth; int w = getWidth(); switch(_shape) { case TRIANGLE: int h = (w>>1); int m = (w>>1)-1; graphics.drawLine(0, h-1, m, 0); graphics.drawLine(m, 0, w-1, h-1); graphics.drawLine(0, h-1, w-1, h-1); textWidth = Math.min(_labelWidth,h); textX = (w - textWidth) >> 1; textY = h >> 1; break; case OCTAGON: int x = 5*w/17; int x2 = w-x-1; int x3 = w-1; raphics.drawLine(0, x, 0, x2); graphics.drawLine(x3, x, x3, x2); graphics.drawLine(x, 0, x2, 0); graphics.drawLine(x, x3, x2, x3); graphics.drawLine(0, x, x, 0); graphics.drawLine(0, x2, x, x3); graphics.drawLine(x2, x3, x3, x2); textWidth = Math.min(_labelWidth, w - 6); textX = (w-textWidth) >> 1; textY = (w-_labelHeight) >> 1; break; case RECTANGLE: default: graphics.drawRect(0, 0, w, getHeight()); textX = 4; textY = 2; textWidth = w - 6; break; } graphics.drawText(_label, textX, textY, (int)( getStyle() & DrawStyle.ELLIPSIS | DrawStyle.HALIGN_MASK ), textWidth ); } |
处理焦点事件
为了支持焦点事件,使用Field.FOCUSABLE样式以及实现Field.moveFocus().如果你想你的Field接收焦点,覆写Field.isFocusable()返回true。
当Field获得焦点时,UI框架调用onFocus(),当Field失去焦点时,调用unFocus().如果你的field对于这些事件需要特定的行为,覆写这些方法。框架调用moveFocus()来处理field的焦点移动事件。它对应trackwheelRoll事件,覆写drawFocus()。
实现set和get方法
Field的get和set方法的实现,增加了Field的能力。
注:所有get和set方法应该在field加入到一个Screen的前后工作。例如,如果现在屏幕上的field合适的调用了invalidate()或updateLayout()setLabel(),应该使用一个新值来修改其显示。
public String getLabel() { return _label; } public int getShape() { return _shape; } public void setLabel(String label) { _label = label; _labelWidth = _font.getAdvance(_label); updateLayout(); } public void setShape(int shape) { _shape = shape; updateLayout(); } |
代码实例
CustomButtonField.java创建了具有多个图形的button field。
实例: CustomButtonField.java
/**
* CustomButtonField.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.docs.custombuttons;
import net.rim.device.api.ui.*;
import net.rim.device.api.system.*;
/**
* CustomButtonField is a class that creates button fields of various
* shapes. This sample demonstrates how to create custom UI fields.
*/
public class CustomButtonField extends Field implements DrawStyle {
public static final int RECTANGLE = 1;
public static final int TRIANGLE = 2;
public static final int OCTAGON = 3;
private String _label;
private int _shape;
private Font _font;
private int _labelHeight;
private int _labelWidth;
/* Constructs a button with specified label, and the default style and shape. */
public CustomButtonField(String label) {
this(label, RECTANGLE, 0);
}
/* Constructs a button with specified label and shape, and the default style. */
public CustomButtonField(String label, int shape) {
this(label, shape, 0);
}
/* Constructs a button with specified label and style, and the default shape. */
public CustomButtonField(String label, long style) {
this(label, RECTANGLE, style);
}
/* Constructs a button with specified label, shape, and style */
public CustomButtonField(String label, int shape, long style) {
super(style);
_label = label;
_shape = shape;
_font = getFont();
_labelHeight = _font.getHeight();
_labelWidth = _font.getAdvance(_label);
}
/* Method that draws the focus indicator for this button and
* * inverts the inside region of the shape.
*
* */
protected void drawFocus(Graphics graphics, boolean on) {
switch(_shape) {
case TRIANGLE:
int w = getWidth();
int h = w >> 1;
for (int i=h-1; i>=2; --i) {
graphics.invert(i, h - i, w - (i << 1), 1);
}
break;
case RECTANGLE:
graphics.invert(1, 1, getWidth() - 2, getHeight() - 2);
break;
case OCTAGON:
int x3 = getWidth();
int x = 5 * x3 / 17;
int x2 = x3 - x;
x3 = x3 - 1;
x2 = x2 - 1;
graphics.invert(1, x, getWidth() - 2, x2 - x + 1);
for (int i=1; i<x; ++i) {
graphics.invert(1+i, x-i,getWidth() - ((i+1)<<1), 1);
graphics.invert(1+i, x2+i,getWidth() - ((i+1)<<1), 1);
}
break;
}
}
/* Returns the label. */
public String getLabel() {
return _label;
}
/* Returns the shape. */
public int getShape() {
return _shape;
}
/* Sets the label. */
public void setLabel(String label) {
_label = label;
_labelWidth = _font.getAdvance(_label);
updateLayout();
}
/* Sets the shape. */
public void setShape(int shape) {
_shape = shape;
updateLayout();
}
/* Retrieves the preferred width of the button. */
public int getPreferredWidth() {
switch(_shape) {
case TRIANGLE:
if (_labelWidth < _labelHeight) {
return _labelHeight << 2;
}
else {
return _labelWidth << 1;
}
case OCTAGON:
if (_labelWidth < _labelHeight) {
return _labelHeight + 4;
}
else {
return _labelWidth + 8;
}
case RECTANGLE: default:
return _labelWidth + 8;
}
}
/* Retrieves the preferred height of the button. */
public int getPreferredHeight() {
switch(_shape) {
case TRIANGLE:
if (_labelWidth < _labelHeight) {
return _labelHeight << 1;
}
else {
return _labelWidth;
}
case RECTANGLE:
return _labelHeight + 4;
case OCTAGON:
return getPreferredWidth();
}
return 0;
}
/* Lays out this button’s contents.
* This field’s manager invokes this method during the layout
* process to instruct this field to arrange its contents, given an
* amount of available space.
**/
protected void layout(int width, int height) {
// Update the cached font in case it has been changed.
_font = getFont();
_labelHeight = _font.getHeight();
_labelWidth = _font.getAdvance(_label);
// Calculate width.
width = Math.min( width, getPreferredWidth() );
// Calculate height.
height = Math.min( height, getPreferredHeight() );
// Set dimensions.
setExtent( width, height );
}
/*
* Redraws this button. The field’s manager invokes this method during the
* repainting process to instruct this field to repaint itself.
*/
protected void paint(Graphics graphics) {
int textX, textY, textWidth;
int w = getWidth();
switch(_shape) {
case TRIANGLE:
int h = (w>>1);
int m = (w>>1)-1;
graphics.drawLine(0, h-1, m, 0);
graphics.drawLine(m, 0, w-1, h-1);
graphics.drawLine(0, h-1, w-1, h-1);
textWidth = Math.min(_labelWidth,h);
textX = (w - textWidth) >> 1;
textY = h >> 1;
break;
case OCTAGON:
int x = 5*w/17;
int x2 = w-x-1;
int x3 = w-1;
graphics.drawLine(0, x, 0, x2);
graphics.drawLine(x3, x, x3, x2);
graphics.drawLine(x, 0, x2, 0);
graphics.drawLine(x, x3, x2, x3);
graphics.drawLine(0, x, x, 0);
graphics.drawLine(0, x2, x, x3);
graphics.drawLine(x2, 0, x3, x);
graphics.drawLine(x2, x3, x3, x2);
textWidth = Math.min(_labelWidth, w - 6);
textX = (w-textWidth) >> 1;
textY = (w-_labelHeight) >> 1;
break;
case RECTANGLE: default:
graphics.drawRect(0, 0, w, getHeight());
textX = 4;
textY = 2;
textWidth = w - 6;
break;
}
graphics.drawText(_label, textX, textY, (int)( getStyle() & DrawStyle.ELLIPSIS | DrawStyle.HALIGN_MASK ),textWidth );
}
}
创建定制的上下文菜单项
在Field类里,创建定制的上下文菜单项。为得到更多关于实现的菜单的信息,参看39页的“显示屏幕”.
private MenuItem myContextMenuItemA = new MenuItem( _resources, MENUITEM_ONE, 200000, 10) { public void run() { onMyMenuItemA(); } }; private MenuItem myContextMenuItemB = new MenuItem( _resources, MENUITEM_ONE, 200000, 10) { public void run() { onMyMenuItemB(); } }; |
提供一个上下文菜单
在主应用程序类里,覆写makeContextMenu()方法提供一个上下文菜单。
protected void makeContextMenu(ContextMenu contextMenu) { contextMenu.addItem(myContextMenuItemA); contextMenu.addItem(myContextMenuItemB); } |
创建应用程序菜单
在主应用程序类里,覆写makeMenu()方法创建应用程序菜单,并且无论合十,当特定的field获取焦点时,更新上下文菜单。
protected void makeMenu(Menu menu) { Field focus = UiApplication.getUiApplication().getActiveScreen() .getLeafFieldWithFocus(); if (focus != null) { ContextMenu contextMenu = focus.getContextMenu(); if (!contextMenu.isEmpty()) { menu.add(contextMenu); menu.addSeparator(); } } } |
代码实例
实例: ContextMenuSample.java
* ContextMenuSample.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.docs.contextmenus;
import net.rim.device.api.i18n.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.system.*;
import com.rim.samples.docs.baseapp.*;
public class ContextMenuSample
extends BaseApp implements ContextMenuSampleResource {
private MyContextField myContextField;
private static ResourceBundle _resources
= ResourceBundle.getBundle(
ContextMenuSampleResource.BUNDLE_ID,
ContextMenuSampleResource.BUNDLE_NAME);
public static void main(String[] args) {
ContextMenuSample app = new ContextMenuSample();
app.enterEventDispatcher();
}
// Inner class to define a new field.
private class MyContextField extends RichTextField {
private MenuItem myContextMenuItemA = new MenuItem(
_resources, MENUITEM_ONE, 200000, 10) {
public void run() {
onMyMenuItemA();
}
};
private MenuItem myContextMenuItemB = new MenuItem(
_resources, MENUITEM_TWO, 200000, 10) {
public void run() {
onMyMenuItemB();
}
};
private void onMyMenuItemA() {
// Perform an action when user selects menu item.
}
private void onMyMenuItemB() {
// Perform an action when user selects menu item.
}
protected void makeContextMenu(ContextMenu contextMenu) {
contextMenu.addItem(myContextMenuItemA);
contextMenu.addItem(myContextMenuItemB);
}
MyContextField(String text) {
super(text);
}
}
protected void makeMenu(Menu menu) {
super.makeMenu(menu, 0); // Implemented by BaseApp.
}
public ContextMenuSample() {
MainScreen mainScreen = new MainScreen();
MyContextField myContextField = new MyContextField(“Field label: “);
mainScreen.add(myContextField);
mainScreen.addKeyListener(this);
mainScreen.addTrackwheelListener(this);
pushScreen(mainScreen);
}
public void onExit() {
// Perform action when application closes.
}
}
创建定制的布局管理器
Manager对象管理UI组件的位置以及决定屏幕上的field如何安排。
创建一个定制的布局管理器
扩展Manager类或其任一子类
class DiagonalManager extends Manager { public DiagonalManager(long style){ super(style); } ... } |
返回一个优先的Field宽度
覆写getPreferredWidth(),以致它能为管理器返回一个优先的Field宽度。
getPreferredWidth()的实现可以返回不同的值,取决于布局管理器的目的。例如,如果管理器扩展了HorizontalFieldManager,getPreferredWidth()返回所有field宽度的总和。如果扩展了VerticalFieldManager,getPreferredWidth()返回最宽field的宽度。
public int getPreferredWidth() { int width = 0; int numberOfFields = getFieldCount(); for (int i=0; i<numberOfFields; ++i) { width += getField(i).getPreferredWidth(); } return width; } |
注:TextField和Manger使用了指派给他们的整个宽度。为组织2个或更多的水平上的对象,分别覆写它们各自的getPreferredWidth()方法。为了组织多个水平上的TextField,覆写layout().
返回一个优先Field高度
覆写getPreferredHeight(),以致它能为管理器返回一个优先的Field高度。
public int getPreferredHeight() { int height = 0; int numberOfFields = getFieldCount(); for (int i=0; i<numberOfFields; ++i) { height += getField(i).getPreferredHeight(); } return height; } |
指定子Field如何安排
subLayout()方法指定了管理器如何在屏幕上组织field。它得到管理器中field的个数,然后为子field设置合适的位置以及布局。
layout()调用了subLayout()方法,subLayout()方法通过调用每个管理器包含的field的setPositionChild ()以及LayoutChild(),控制每个子field如何加到屏幕上。
protected void sublayout(int width, int height) { int x = 0; int y = 0; Field field; int numberOfFields = getFieldCount(); for (int i=0; i<numberOfFields; ++i) { field = getField(i); layoutChild(field, width, height); setPositionChild(field, x, y); field.setPosition(x,y); x += field.getPreferredWidth(); y += field.getPreferredHeight(); } setExtent(width,height); } |
注:为设置field需要的大小,在subLayout()方法里调用setExtend()。如果你不调用setExtend(),则不会绘制每个field,并且也不抛出一个异常。
处理焦点
当用户滚动滑轮时,为了指定field该如何得到焦点,覆写nextFocus()方法。direction参数描述了焦点移动的方向(一般来说,当滑轮向下滚动,焦点向下并且向右方向。当滑轮向上滚动,焦点向上并且向左)。
protected int nextFocus(int direction, boolean alt) { int index = this.getFieldWithFocusIndex(); if(alt) { if(direction > 0) { // action to perform if trackwheel is rolled up } else { // action to perform if trackwheel is rolled down } } if (index == this.getFieldWithFocusIndex()) return super.nextFocus(direction, alt); else return index; } |
为了将焦点转移到下一个field,而以管理器的顺序,这个field不是下一个field,那么覆写nextFocus().例如,如果你想为你的管理器实现Page-up和Page-down的功能,那么nextFocus()就有用了。
当可见区域改变时重绘field
缺省的,定制的管理器在不考虑剪辑区域下调用paint()重绘所有field。如果这导致了不必要的重绘,当可见区域改变时,仅subpaint()的实现重绘所有field。
代码实例
例: DiagonalManager.java
/**
* DiagonalManager.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.docs.custommenu;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
class DiagonalManager extends Manager {
public DiagonalManager(long style) {
super(style);
}
public int getPreferredWidth() {
int width = 0;
int numberOfFields = getFieldCount();
for (int i=0; i<numberOfFields; ++i) {
width += getField(i).getPreferredWidth();
}
return width;
}
public int getPreferredHeight() {
int height = 0;
int numberOfFields = getFieldCount();
for (int i=0; i<numberOfFields; ++i) {
height += getField(i).getPreferredHeight();
}
return height;
}
protected void sublayout(int width, int height) {
int x = 0;
int y = 0;
Field field;
int numberOfFields = getFieldCount();
for (int i=0; i<numberOfFields; ++i) {
field = getField(i);
layoutChild( field, width, height );
setPositionChild(field, x, y);
x += field.getPreferredWidth();
y += field.getPreferredHeight();
}
setExtent(width,height);
}
protected int nextFocus(int direction, boolean alt) {
int index = this.getFieldWithFocusIndex();
if(alt)
{
if(direction > 0)
{
// Action to perform if trackwheel is rolled up.
}
else
{
// Action to perform if trackwheel is rolled down.
}
}
if (index == this.getFieldWithFocusIndex())
return super.nextFocus(direction, alt);
else
return index;
}
}
创建列表
一个ListField包含了多列可选项。为了使用户可以选择列表中多项,声明列表为MULTI_SELECT.
创建一个回调对象
ListFieldCallback对象为列表控制所有重绘任务。每次要求Field显示列表中的一个条目。必要的方法也会在回调对象中调用。
ListFieldCallback接口的实现创建了一个回调对象。系统调用这个接口的方法绘制列表的行,获得一个指定的列表元素,或决定列表的宽度。
private class ListCallback implements ListFieldCallback { // The listElements vector contain the entries in the list. private Vector listElements = new Vector(); ... } |
允许Field重绘一行
drawListRow()的实现允许Field重绘一行。传递到drawListRow()的图形上下文代表整个列表。相应地,drawText()必须指明绘制哪一行。
public void drawListRow(ListField list, Graphics g, int index, int y, int w) { String text = (String)listElements.elementAt(index); g.drawText(text, 0, y, 0, w); } |
允许Field从列表中得到一个条目(Entry)
get()的实现允许field从列表中得到一个条目。本方法返回一个包含在有明确索引行中的对象。
public Object get(ListField list, int index) { return listElements.elementAt(index); } |
为列表返回一个优先的宽度
getPreferredWidth()的实现为列表返回一个优先的宽度。在下面的实现中,getPreferredWidth()返回整个屏幕的绘制宽度。
getPreferredWidth()的实现返回一个不同的值,这依赖field管理器的类型。例如,如果管理器扩展了HorizontalFieldManager,getPreferredWidth()返回所有field宽度的总和。如果扩展了VerticalFieldManager,getPreferredWidth()返回最宽field的宽度。
public int getPreferredWidth(ListField list) { return Graphics.getScreenWidth(); } |
指派回调以及加入条目到列表中
创建列表对象,并且将回调指派这个对象。
创建列表对象
为了列表创建ListField对象以及ListCallback对象。
注:ListCallback是一个定制的ListFieldCallback类,这个类在66页的“创建一个回调对象”中创建。
ListField myList = new ListField(); ListCallback myCallback = new ListCallback(); |
设置回调
调用setCallback()将ListFieldCallback与ListField关联。这个关联允许回调增加列表项到列表中。
myList.setCallback(myCallback); |
增加列表条目
为了将条目增加到列表中,创建条目,并指定一个索引,并在这个索引上插入每个条目到ListField对象中。然后每个ListField对象到ListFieldCallback中。
String fieldOne = new String("Field one label"); String fieldTwo = new String("Field two label"); String fieldThree = new String("Field three label"); myList.insert(0); myList.insert(1); myList.insert(2); myCallback.insert(fieldOne, 0); myCallback.insert(fieldTwo, 1); myCallback.insert(fieldThree, 2); mainScreen.add(myList); |
代码实例
例:SampleListFieldCallback.java
/**
* SampleListFieldCallback.java
* Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved.
*/
package com.rim.samples.docs.listfields;
import java.util.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
public class SampleListFieldCallback
extends UiApplication {
private ListField myList;
public static void main(String[] args) {
SampleListFieldCallback app = new SampleListFieldCallback();
app.enterEventDispatcher();
}
private static class ListCallback implements ListFieldCallback {
private Vector listElements = new Vector();
public void drawListRow(ListField list, Graphics g, int index, int y, int w)
{
String text = (String)listElements.elementAt(index);
g.drawText(text, 0, y, 0, w);
}
public Object get(ListField list, int index) {
return listElements.elementAt(index);
}
public int indexOfList(ListField list, String p, int s) {
return listElements.indexOf(p, s);
}
public int getPreferredWidth(ListField list) {
return Graphics.getScreenWidth();
}
public void insert(String toInsert, int index) {
listElements.addElement(toInsert);
}
public void erase() {
listElements.removeAllElements();
}
}
public SampleListFieldCallback() {
MainScreen mainScreen = new MainScreen();
myList = new ListField();
ListCallback myCallback = new ListCallback();
myList.setCallback(myCallback);
String fieldOne = “ListField one”;
String fieldTwo = “ListField two”;
String fieldThree = “ListField three”;
myList.insert(0);
myCallback.insert(fieldOne, 0);
myList.insert(1);
myCallback.insert(fieldTwo, 1);
myList.insert(2);
myCallback.insert(fieldThree, 2);
mainScreen.add(myList);
pushScreen(mainScreen);
}
}
}
操作图片
使用未处理(raw)的图像数据
为了从图像的特定区域获取未处理的图像数据,并存储在一个整数数组中,调用Bitmap.getARGB().应用程序然后可以直接对未处理的图像数据进行操作。
void getARGB(int[] argbData, int offset, int scanLength,
int x, int y, int width, int height);
注:getARGB()方法只在彩屏的BlackBerry设备适用。
参数 | 描述 |
argbData | 整数数组,用来存储ARGB数据;每个象素都以0xAARRGGBB的格式存储。 |
offset | 数据的偏移,从这里开始读取。 |
scanLength | 数据数组内每一个扫描行的宽。 |
x | 矩形的左边,从这里开始读取图像数据。 |
y | 矩形的上边,从这里开始读取图像数据。 |
width | 矩形的宽,从这里开始读取图像数据。 |
height | 矩形的高,从这里开始读取图像数据。 |
设备模拟器显示图像数据时每一个象素作为一个整数,每个象素中,每个字符(不透明)有8个位,红,绿以及蓝的值。颜色由8个整数以0xAARRGGBB的形式组成。
获取图像数据
初始化一个整型数组,然后调用Bitmap.getARGB()将新的或预定义的位图的未处理的图像数据存储到整型数组中。
Bitmap original = Bitmap.getPredefinedBitmap(Bitmap.INFORMATION); int[] argb = new int[original.getWidth() * original.getHeight()]; original.getARGB(argb, 0, original.getWidth(), 0, 0, original.getWidth(), original.getHeight()); |
比较2个图像
调用Bitmap.equals()决定2个位图是否相同。
if(restored.equals(original)) { System.out.println("Success! Bitmap renders correctly with RGB data."); } else if(!restored.equals(original)) { System.out.println("Bitmap rendered incorrectly with RGB data."); } |
使用编码的图像
net.rim.device.api.system.EncodedImage类封装了各种格式的编码图像。BlackBerry设备支持下面的图像格式:.gif,.png,.wbmp,以及.jpeg.只有彩屏的BlackBerry设备才支持.jpeg图像。
注:JPEGEncodedImage类需要一个不可用的签名。
使用EncodedImage的子类,PNGEncodedImage和WBMPEncodedImage,来分别访问.png和.wbmp图像的特定属性。例如,PNGEncodedImage提供方法来获得图像的色彩深度(Bit Depth),alpha 通道(alpha channel[2]),以及颜色类型。
在BlackBerry IDE中,一个应用程序能够直接访问加到工程或者依赖的类库工程中的图像。
访问一个图像
在BlackBerry IDE中,保存一个图像到你的项目文件夹或者子文件夹,然后增加图像到工程中。调用Class.getResourceAsStream()获取图像作为一个字节的输入流。
private InputStream input; ... try { input = Class.forName("com.rim.samples.docs.imagedemo.ImageDemo"). getResourceAsStream("/images/example.png"); } catch (ClassNotFoundException e) { System.out.println("Class not found"); } |
解码一个图像
为了编码一个图像,调用EncodedImage.createEncodedImage()。这个方法使用字节数组里的未处理的图像数据来创建了一个EncodedImage的实例。如果作为参数的字节数组布包汉一个可以识别的图像格式,它将抛出一个IllegalArgumentException异常。
private byte[] data = new byte[2430];// Store the contents of the image file. try { input.read(data); // Read the image data into the byte array. } catch (IOException e) { // Handle exception. } try { EncodedImage image = EncodedImage.createEncodedImage(data, 0, data.length); } catch (IllegalArgumentException iae) { System.out.println("Image format not recognized."); } |
注:缺省地,BlackBerry设备软件监测基于图像格式的MIME类型的图像。如果正确的MIME类型未能自动的监测到,使用下面EncodedImage.createEncodedImage()的形式指定一个特定的MIME类型:
createEncodedImage(byte[] data, createEncodedImage(byte[] data, int offset, int length, String mimeType)
如果图像格式预指定的MIME类型不匹配,这个方法抛出一个IllegalArgumentException异常。支持的MIME类型包括:image/gif, image/png, image/vnd.wap.wbmp, 以及 image/jpeg.
显示一个编码的图像
调用BitmapField.setImage()指定一个编码的图像到一个BitmapField,然后调用add()将BitmaoField加入到屏幕中。
BitmapField field = new BitmapField(); field.setImage(image); add(field); |
设置解码模式
调用EncodedImage.setDecodeMode()来设置图像的解码模式。提供下面模式之一作为方法的一个参数:
解码模式 | 描述 |
DECODE_ALPHA | 解码一个alpha通道,如果一个存在(这是缺省的模式)。 |
DECODE_NATIVE | 强制将位图解码未手持设备软件的原生位图类型。 |
DECODE_READONLY | 将解码的位图标记为只读。 |
设置缩放因子(scaling factor)
当解码时,为了设置用在缩减一个图像的整数因子,调用EncodedImage.setScale()。
图像通过作为scale参数的整型来缩放。例如,如果你设置缩放因子为2,图像将缩小到原大小的50%。
代码实例
ImageDemo.java实例从一个包含在项目中的图像获得未处理的数据,然后使用这个未处理的数据来重新创建一个EncodedImage。
例:ImageDemo.java
/**
* ImageDemo.java
*/
package com.rim.samples.docs.imagedemo;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.system.*;
import java.io.*;
/*
* The ImageDemo.java sample retrieves raw data from an image that
* is included in its project, and then uses that raw data to
* recreate an EncodedImage.
*/
public class ImageDemo extends UiApplication {
public static void main(String[] args) {
ImageDemo app = new ImageDemo();
app.enterEventDispatcher();
}
public ImageDemo() {
pushScreen(new ImageDemoScreen());
}
}
final class ImageDemoScreen extends MainScreen {
private static final int IMAGE_SIZE = 2430;
private InputStream input;
private byte[] data = new byte[IMAGE_SIZE];
public ImageDemoScreen() {
super();
setTitle(new LabelField(“Image Demo Sample”));
try {
input = Class.forName(“com.rim.samples.docs.imagedemo.ImageDemo”)
.getResourceAsStream(“/images/hellokitty.png”);
}
catch (ClassNotFoundException e) {
System.out.println(“Class not found”);
}
if(input == null) {
System.out.println(“Error: input stream is not initialized.”);
}
else if (input != null) {
System.out.println(“OK: input stream is initialized.”);
try {
int code = input.read(data);
System.out.println(“Total number of bytes read into buffer: “ + code + “ .”);
}
catch (IOException e) {
// Handle exception.
}
try {
EncodedImage image = EncodedImage.createEncodedImage(data, 0, data.length);
add(new BitmapField(image.getBitmap()));
}
catch (IllegalArgumentException iae) {
System.out.println(“Image format not recognized.”);
}
}
}
}
使用图像对象画图
Graphics 对象允许应用程序完成绘图功能和描绘(rendering)操作。使用Graphics类绘制整个屏幕或一个BitmapField。如果你的应用程序不包含任何field,调用Screen.getGraphics()来获得整个屏幕的绘图上下文。
为了绘制一个指定的BitmapField,应用程序通过传入一个field到Graphics的 构造子中,来为一个指定的field获得一个绘图上下文.当绘制一个BitmapField时,field管理器在field重绘时传递一个绘图上下文给field。为了完成绘制一个定制的field,当你扩展Field类时覆写Graphics.paint()方法。
Graphics类允许你绘制图形,例如弧线,直线,矩形以及圆。
使用图形上下文
为了利用Graphics类绘制,为每各自的field或整个屏幕获得一个图形上下文。
为了为各自的Field获取一个图形上下文,调用Graphics的构造子。
Bitmap surface = new Bitmap(100, 100); BitmapField surfaceField = new BitmapField (surface); Graphics graphics = new Graphics(surface); |
为了为整个屏幕获得一个图形上下文,调用Screen.getGraphics()。
Graphics graphics = Screen.getGraphics(); |
为使用任何图形上下文绘图,你的方法务必要在field或屏幕的界限内完成它们的绘制功能。
graphics.fillRect(10, 10, 30, 30); graphics.drawRect(15, 15, 30, 30); |
如果你的图形上下文没有应用到整个屏幕,加入BitmapField到屏幕中。
mainScreen.add(surfaceField); |
创建一个与标准的 BlackBerry UI一致的界面
DrawStyle接口提供了Graphics和Field对象使用的接口。DrawStyle的实现允许你创建一个与标准BlackBerry UI一致的接口。如果你正扩展Field类来创建一个定制的Field,你的代码需要接受合适的样式,这样它与标准的BlackBerry应用程序类似。
DrawStyle作为style参数应用在field上,如下面的例子:
ButtonField buttonField = new ButtonField(DrawStyle.ELLIPSIS); |
你可以在下面的对象中使用DrawStyle元素:
- BitmapField
- ButtonField
- DateField
- Graphics
- LabelField
- ObjectListField
用颜色绘制
利用彩色绘制只在彩屏的BlackBerry设备上适用。为了判断BlackBerry设备是否支持彩色显示,调用Graphics.isColor().为了决定BlackBerry设备支持的颜色象素,调用Graphics.numColors()。
设置alpha值
全局alpha值决定他和绘制区域中象素的透明度,0(0x0000)是完全透明(不可见),255(0x00FF)是完全不透明。为了设置或得到全局alpha值,调用Graphics.setGlobalAlpha()或Graphics.getGlobalAlpha().
注:BlackBerry为特定的光栅操作使用alpha值。文本和绘制操作不会用到。
决定光栅操作的支持
为决定一个Graphics对象是否支持一个特定的光栅操作,调用Graphics.isRopSupported(int),使用下面提供的常数之一作为参数。
常数 | 光栅操作 |
ROP_CONST_GLOBALALPHA | 将一个使用一个全局alpha常量值的前台常量颜色和目标象素混合。 |
ROP_SRC_GLOBALALPHA | 将一个使用一个全局alpha常量值的源位图和目标象素混合。 |
绘制一个路径(Path)
为了绘制一组阴影填充的路径,调用Graphics.drawShadedFilledPath():
public void drawShadedFilledPath(
int[] xPts,
int[] yPts,
byte[] pointTypes,
int[] colors,
int[] offsets)
参数 | 描述 |
xPts | 有序的列表定义了每个在路径里的顶点的x值。 |
yPts | 有序的列表定义了每个在路径里的顶点的y值。 |
pointTypes | 为每个定义的(x,y)点指定一个下面的常数。如果pointTypes为null,所有点缺省为Graphics.CURVEDPATH_END_POINT.
|
colors | 有序的列表为每个顶点以0x00RRGGBB格式定义颜色值。如果是null,将绘制一个以当前前景颜色的路径。 |
offsets | 列表在xPts和yPts数组里,定义了每个路径的起点。null描述了单个路径,这个路径的起点在(xPts[offsets[i]],yPts[offsets[i]]),终点在(xPts[offsets[i+1]]-1,yPts[offsets[i+1]]-1)。 |
下面的例子绘制了一个从蓝色到红色混合的路径。
Bitmap surface = new Bitmap(240, 160); BitmapField surfaceField = new BitmapField(surface); add(surfaceField); Graphics graphics = new Graphics(surface); int[] X_PTS = { 0, 0, 240, 240 }; int[] Y_PTS = { 20, 50, 50, 20 }; int[] drawColors = { 0x0000CC, 0x0000CC, 0xCC0000, 0xCC0000 }; try { graphics.drawShadedFilledPath(X_PTS, Y_PTS, null, drawColors, null); } catch (IllegalArgumentException iae) { System.out.println("Bad arguments."); } |
使用绘制格式
为了将绘制格式打开或关闭,调用Graphics.setDrawingStyle(int drawStyle, Boolean on),在这里,on指定是否打开(true)或关闭(false)绘制格式.为了判断一个绘制格式是否已经设置,调用Graphics.isDrawingStyleSet(int drawStyle).
常数 | 描述 |
DRAWSTYLE_AALINES | 直线的访混淆图像的格式,通过setDrawingStyle()和 isDrawingStyleSet()使用。 |
DRAWSTYLE_AAPOLYGONS | 多边形的访混淆图像的格式,通过setDrawingStyle()和 isDrawingStyleSet()使用。 |
DRAWSTYLE_FOCUS | 当绘制已完成,系统为焦点绘制设置的格式。 |
DRAWSTYLE_SELECT | 当绘制已完成,系统为选择绘制设置的格式。 |
像印花一样使用单色位图field
通过用颜色提交不透明的区域,STAMP_MONOCHROME选项允许应用程序使用单色位图,如同印花一样。这个选项用于位图,这个位图是1位的,并且有定义的alpha。
BitmapField field = new BitmapField(original, BitmapField.STAMP_MONOCHROME); |
从未处理的数据绘制一图像
- 创建一空的位图。在本例中,类型和大小都复制到一个已经存在的位图。
- 使用新建的位图创建一个Graphics对象作为绘图表面。
- 调用Graphics.rawRGB(),并使用从源处来的未处理的数据绘制一个新的图像。
Bitmap restored = new Bitmap(original.getType(), original.getWidth(), original.getHeight()); Graphics graphics = new Graphics(restored); try { graphics.drawRGB(argb, 0, restored.getWidth(), 0, 0, restored.getWidth(), restored.getHeight()); } catch(Exception e) { System.out.println("Error occurred during drawing: " + e); } } |
使用位图类型
注:下面关于位图类型的详情只提供类型信息。应用程序应不要依赖位图的实际位格式作为格式,因在手持设备的软件的未来版本可能会变化。
为了决定Bitmap类型,调用Bitmap.getType()。这个方法返回下面常数中的一个:
位图类型 | 描述 |
COLUMNWISE_MONOCHROME | 数据存储在列中,每个象素一个位:0是白色,1是黑色。在一个字节里,最上方的象素在低的有效字节里。低有限的字节包含了一列里的最上方的象素。 |
ROWWISE_MONOCHROME | 数据存储在行里,一个象素一个位:0代表黑色,1代表白色。在宽度上,每行是4个字节的倍数。在一个字节里,最左边的象素在低的有效字节里。低有限的字节包含了一行里的最左边的象素。 |
ROWWISE_16BIT_COLOR | 据存储在行里,一个象素有2个字节:0是黑色,0xffff(65535)是白色。在宽度上,每行是4字节的倍数。 |
- 在黑白屏幕的BlackBerry设备上,数据存储在列中,因此,Bitmap.getType()返回COLUMNWISE_MONOCHROME。头2个字节代表了位图第一个列里的头16个象素。
- 在彩屏的BlackBerry设备上,数据保存在行里,因此itmap.getType()为黑白图片返回ROWWISE_MONOCHROME,为彩色图片返回ROWWISE_16BIT_COLOR。在黑白图片里,头2个字节代表了位图的第一行的头16个象素,从左到右。在彩色图片里,头2个字节代表了第一个象素。
下面2个Bitmap的构造子允许你指定一个type参数:
- Bitmap(int type, int width, int height)
- Bitmap(int type, int width, int height, byte[] data)
为了获取BlackBerry设备的缺省位图类型,调用静态方法:
Bitmap.getDefaultType()
代码实例
DrawDemo.java从预定义的位图里获取未处理的数据,然后使用这些数据绘制一个新的位图。最后显示原始的和恢复的图像。
例:DrawDemo.java
/*
* DrawDemo.java
* Copyright (C) 2002-2005 Research In Motion Limited.
*/
package com.rim.samples.docs.drawing;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
/* The DrawDemo.java sample retrieves raw data from a predefined bitmap
image, and then draws a new bitmap using the data. It then displays
the original and restored images. */
public class DrawDemo extends UiApplication {
public static void main(String[] args) {
}
DrawDemo app = new DrawDemo();
app.enterEventDispatcher();
}
public DrawDemo()
{
pushScreen(new DrawDemoScreen());
}
final class DrawDemoScreen extends MainScreen {
public DrawDemoScreen()
{
super();
LabelField title = new LabelField(“UI Demo”, LabelField.USE_ALL_WIDTH);
setTitle(title);
Bitmap original = Bitmap.getPredefinedBitmap(Bitmap.INFORMATION);
Bitmap restored = new Bitmap(original.getType(), original.getWidth(),
original.getHeight());
Graphics graphics = new Graphics(restored);
// Retrieve raw data from original image.
int[] argb = new int[original.getWidth() * original.getHeight()];
original.getARGB(argb, 0, original.getWidth(), 0, 0,
original.getWidth(),original.getHeight());
// Draw new image using raw data retrieved from original image.
try
{
graphics.drawRGB(argb, 0, restored.getWidth(), 0, 0,
restored.getWidth(),restored.getHeight());
}
catch(Exception e) {
System.out.println(“Error occurred during drawing: “ + e);
}
if(restored.equals(original))
{
System.out.println(“Success! Bitmap renders correctly with RGB data.”);
}
else if(!restored.equals(original))
{
System.out.println(“Bitmap rendered incorrectly with RGB data.”);
}
BitmapField field1 = new BitmapField(original, BitmapField.STAMP_MONOCHROME);
BitmapField field2 = new BitmapField(restored);
add(new LabelField(“Original bitmap: “));
add(field1);
add(new LabelField(“Restored bitmap: “));
add(field2);
}
}
监听UI对象的改变
UI EventListeners允许应用程序响应一个UI对象的改变。这里有3种类型的UI事件监听者:
监听者 | 描述 |
FieldChangeListener | 当field的属性改变时发出事件通知 |
FocusChangeListener | 当Field获取或失去焦点时发出事件通知。 |
ScrollChangeListener | 当一个管理器的水平或者垂直滚动值变化时发出事件通知。 |
监听field属性的变化
为了监测field的变化,实现FieldChangeListener接口。调用setChangeListener()来把你的实现指派给一个field。
private class FieldListener implements FieldChangeListener { public void fieldChanged(Field field, int context) { if (context != FieldChangeListener.PROGRAMMATIC) { // Perform action if user changed field. } else { // Perform action if application changed field. } } } // ... FieldListener myFieldChangeListener = new FieldListener() myField.setChangeListener(myFieldChangeListener); |
监听焦点的改变
为了监测field之间焦点的改变,指派给他们一个FocusChangeListener。实现这个FocusChangeListener,然后通过调用setChangeListener()把你的实现指派给一个Field。一个FocusChangeListener关心一个与之相关的明确的Field的焦点的获取,失去或改变。
当field通过实现focusChanged()获取,失去或改变焦点时, FocusChangeListener的实现应该指明field将采取什么样的动作。
private class FocusListener implements FocusChangeListener { public void focusChanged(Field field, int eventType) { if (eventType == FOCUS_GAINED) { // Perform action when this field gains the focus. } if (eventType == FOCUS_CHANGED) { // Perform action when the focus changes for this field. } if (eventType == FOCUS_LOST) { // Perform action when this field loses focus. } } } FocusListener myFocusChangeListener = new FocusListener(); myField.setChangeListener(myFocusChangeListener); |
监听滚动事件
ScrollChangeListener接口的实现允许你的field管理器管理滚动事件,调用setScrollListener()将你的实现给一个Manager。当水平或垂直的(或都有)滚动值发生变化时,scrollChanged()方法传递一个新的值。
注:典型地,监听滚动变化没有必要,因为你的应用程序可以监听field的焦点变化;尽管这样,ScrollChangeListener在游戏实现中可能有用。
为将监听者指派给一个field,调用field管理器上的setScrollListener().
private class ScrollListener implements ScrollChangeListener { scrollChanged(Manager manager, int newHoriztonalScroll, int newVerticalScroll){ // Perform action. } } ScrollListener myScrollChangeListener = new ScrollListener(); myManager.setScrollListener(myScrollChangeListener); |