17 November 2009
需要对 Flex、MXML 和 ActionScript 3 有所了解。了解 Cairngorm 框架会有所帮助,但不是必须的。
中级
Adobe AIR 2 引入了辅助功能支持,将 Flex 框架和您的自定义组件向屏幕阅读器软件敞开大门。您现在可以将 AIR 应用程序设计为可供视觉方面存在障碍的人士使用。通过将这一新增功能与现有的布局和 UI 选项结合使用,您可以提供可由更多用户使用的 AIR 应用程序,其中包括有视觉障碍的用户和失明用户。
这一功能的核心是 AIR 2 内建的奇偶校验,底层提供 Flex 和 Flash 辅助功能支持。这意味着,作为开发人员,您可以作出引向可访问应用程序的设计决策,而不是对于依赖于屏幕阅读器(如 Freedom Scientific 的 JAWS)的个人而言不切实际的应用程序。我将解释这些设计决策并告诉您如何使应用程序能通过键盘实现导航、使用数据绑定以编程方式调整字体大小以及配置在修改后的 Flex 组件中读取哪些文本。
我谈到的许多示例通过范例应用程序 Quoth the Twitter 进行实施。您可以下载这些范例文件并使用自己喜欢的 IDE 进行尝试。注意,JAWS 和 Microsoft Narrator(Windows 随附的一款文本转语音实用程序)仅限于 Microsoft Windows。如果您在 Mac OS X 或 Linux 上开发,我建议在运行 VMware 的 Windows XP 虚拟实例中使用屏幕阅读器测试您的应用程序。
注意:本文中使用的范例代码也可以作为开放源代码应用程序获得。您可以通过 Google Code 上的代码库访问最新版本;也可以从 AIR 文件直接安装应用程序。
创建可访问的 AIR 应用程序首先要启用 AIR 2 中的辅助功能支持。这一辅助功能使支持辅助功能的现有组件能向桌面屏幕阅读器敞开大门。启用它时,本机支持辅助功能的 Flex 组件(如 Label、TextInput 或 Button)将自动向辅助功能 API 公开它们的属性。底层框架提供的钩子同样如此,可通过 AccessibilityProperties 类访问它们,这样您就可以控制 Flash 对象对辅助功能帮助的表示。
要启用辅助功能支持,您必须使用辅助功能进行编译。这在 Flash Builder 4 测试版中很简单:
如果您在其他 IDE 或命令行中编译项目,您要为 Flex 编译器将另一个参数传递给 AIR 包装器:--accessible 或 --compiler.accessible
例如,本教程范例应用程序的编译命令如下:
./amxmlc --accessible --library-path+=libs/ --source-path+=../as3preferenceslib/src,../as3corelib/src QuothTheTwitter.mxml
您的环境中路径可能不同,并且在 Windows 上,指定命令行参数时使用一个连字符。
启用辅助功能后,下一步是将应用程序设计为充分利用这个新增功能。幸运的事,这很简单,因为 Flex 3 提供与辅助功能框架兼容的 28 个组件。Flex 4 组件(又称为 Spark 组件)也有辅助功能,但截止本文发稿时它尚未实施。有关可访问的 Flex 3 组件的列表,请参阅 Adobe Flex 3 帮助。
本文的范例应用程序使用许多公共组件:Label、Button、TextInput、Accordion、List 以及许多布局组件(包括 HBox、VBox 和 Spacer)。所有这些组件无需修改即可访问,即使应用程序改变状态、启用或禁用组件、或是播放效果时。
为辅助功能设计或修改应用程序时,想象一下有视觉障碍的用户如何导航和理解它的界面。没有键盘支持、使用固定字体大小、只通过颜色传达信息或以十分相似的颜色表现相邻项的应用程序对于有视觉障碍的用户而言更难使用。这些问题并不是 AIR 2 特有的,但是要创建一个可访问的 AIR 应用程序,您必须将这些问题的解决方案融入您的设计中。
键盘导航的主要考虑事项是跳位顺序。跳位顺序决定了用户与应用程序的交互顺序,以及屏幕阅读器的组件阅读方式(阅读顺序)。跳位顺序由 SWF 文件包含的 InteractiveObject 类的每个实例的 tabIndex 属性决定,根据这些对象的顺序自动设置它。因此,Flash Player 经常会按对于用户而言似乎不正确的顺序指定索引-主要因为它不考虑对象层次。
要解决这个问题,可以在跳位顺序不正确的任何 InteractiveObjects 上手动设置 tabIndex 属性。每次修改应用程序的用户界面时重置跳位索引很快会令人厌烦,所以我建议在设计流程的稍后阶段设置它们。并且,您可能希望跳过索引,为稍后添加组件留出空间。如果要避免开发过程中的索引重新排序问题,您可以将数据绑定作为将数字映射到组件的临时解决方案。使用 MVC Cairngorm 框架(如排列)可能如下:
<mx:Button id="changeUsername" click="onClick(event)" tabIndex="{ModelLocator.getInstance().changeUsernameTabIndex}"/>
对 ModelLocator 包含的 changeUsernameTabIndex 常量的引用是关键所在:Flex 将在运行时把正确值分配给 Button 的 tabIndex 属性,使您能在 ModelLocator 中保留一系列跳位索引。随后,可以在开发的稍后阶段删除数据绑定,因为要避免绑定应用程序执行过程中不变的属性所产生的系统开销。
我在这个范例应用程序中并未采用这种方法,但您可能希望通过一个中央位置管理较大项目的索引。
如果要防止 Tab 键将焦点放在特定组件上,可以将 tabEnabled 和 focusEnabeld 属性设置为 false。注意,这不会使屏幕阅读器无法读取组件。
为键盘用户提供常见任务快捷键也很实用。快捷键可以是全局或基于组件。在前一个例子中,您只需指定 KeyDown 事件处理函数,并在其中检查用户的输入是否为识别的快捷键。例如:
protected function titlewindow1_keyDownHandler(event:KeyboardEvent):void
{
if ( event.keyCode == Keyboard.ESCAPE )
{
close();
}
}
这类简单快捷键可以为所有用户简化应用程序。
您也可以使用 NativeMenuItem 创建一个 NativeMenu,由操作系统处理快捷键。这是一个创建全局快捷键的简单方式。要使 NativeMenu 和 NativeMenuItem 能通过键盘访问,应设置 mnemonicIndex 属性以指定助记符在菜单项标签中的位置,然后设置 keyEquivalent 属性,使菜单也能通过 Control 或 Command 键快捷键进行访问。以下示例根据 AIR 应用程序 ShareFire 改编而成:
if ( !NativeWindow.supportsMenu ) return;
var nm:NativeMenu = new NativeMenu();
// _A_dd Feed Button -- the location of the mneomonic index
var nmi:NativeMenuItem = new NativeMenuItem("Add Feed");
nmi.addEventListener(Event.SELECT,
function(e:Event):void
{
// Button pressed. Open the Feed Drawer
ModelLocator.getInstance().isFeedDrawerOpen = true;
});
nmi.mnemonicIndex = 0;
nmi.keyEquivalent = "a";
nm.addItem(nmi);
…
NativeApplication.nativeApplication.activeWindow.menu = nm;
执行此代码时,它将创建一个 NativeMenu 对象,添加一个 NativeMenuItem 对象,最后将这个菜单分配到 activeWindow 引用的 NativeWindow。用户可通过两种方式访问 NativeMenuItem:通过键修饰符(Windows 中的 Alt)和 mneomicIndex 表明的字母导航菜单,或同时按下字母键和键等效修饰符(Windows 或 Linux 上的 Ctrl 键以及 Mac OS X 上的 Command 键)。通过 mnemonicIndex 分配的字母在 Windows 上表示为一个下划线字符,在不允许使用这些索引的菜单导航的操作系统上则被忽略。而 keyEquivalent 属性是一个可用于所有操作系统的键盘快捷键。
允许用户在您的应用程序中更改字体大小提高了可读性和易用性,这与允许用户更改组件的外观一样。用户可能发现美学上令人愉悦、易于读取的颜色方案对于色盲用户就像小字体对于其他用户一样难读。因此,您必须构建一个功能,它允许用户调节字体大小并增强组件的对比度和颜色。
创建字体大小可调节的应用程序很简单,并且使用 Cairngorm 等 MVC 框架实施这类应用程序也很简单。首先创建用于存储程序的最小、最大和当前字体大小的变量。在 Cairngorm 中,这些变量可以放入 ModelLocator 中:
public const MIN_FONT_SIZE:uint = 10;
public const MAX_FONT_SIZE:uint = 30;
[Bindable] public var fontSize:uint;
fontSize 变量是 Bindable,因此当它更改时将调用事件监听器。定义最小和最大字体大小的变量是常量。
注意:我将所有这些变量定义为 uint,因为据称 uint 变量的性能在用作循环迭代时略高于 Number 或 int;在从不使用非正值的地方常用它们并无大碍。(并且当您养成这个习惯时,int 的性能依然比 Number 高。)
第二步是在 fontSize 变量更改时,使您的应用程序作出回应。在您的主应用程序中的某个根级组件中通过数据绑定完成它。要做到这一点,最简单和最高级的方法是将 mx:WindowedApplication 组件的 fontSize 属性绑定到 Model 中的 fontSize 变量。mx:WindowedApplication 标签中的 MXML 如下:
fontSize="{ModelLocator.getInstance().fontSize}"
第三步是在应用程序启动时初始化这个变量。它允许您为 fontSize 属性重新调用之前的值。在范例应用程序中,它存储在加密的本地存储中并使用 Preference 对象访问,这是 as3preferenceslib 库提供的一个便利类。将所有这些初始化步骤放在一个中央位置是一个好主意。在 Cairngorm 中,您可以将它们放在一个 InitCommand 对象中,该对象实施 ICommand 接口并执行以下代码:
ml:ModelLocator = ModelLocator.getInstance();
ml.fontSize = ml.prefs.getValue("fontSize",ml.MIN_FONT_SIZE);
它使用 ModelLocator 中的 prefs Preference 对象,并检查名为 fontSize 的值。如果找到这个值,将为 ml.fontSize 分配它的值;否则,将 ml.fontSize 分配给之前定义的 MIN_FONT_SIZE 常量。
最后一步是允许用户更改应用程序中的 fontSize 变量。这里可以使用几个控件中的任何一个,包括 TextInput、DropDownList 或 Slider。对 fontSize 作出任何更改将通过整个应用程序的字体大小自动反映出来。使用标签创建 Slider 有点复杂,本文篇幅有限,但您可以参考以下文件中的 fontSizeSlider HSlider 组件: Settings.mxml 查看示例。
概括而言,以下是使用可调节的字体大小创建应用程序的四个步骤:
fontSize 属性绑定到 fontSize 变量。 您可以分别在以下文件中查看实施的这些步骤: ModelLocator.as、 QuothTheTwitter.mxml、 InitCommand.as和 Settings.mxml 。
您能以编程方式为 Flex 3 组件载入或卸载 CSS 声明,通过 Skin 类自定义 Flex 4 组件,增强 AIR 中组件的对比度。
注意:Flex 4 仍包含 mx(又称为 Halo)组件,但在样式设置行为方面有所不同,需要采用一些变通方法。例如,可以使用 backgroundColor 属性为 Flex 3 中的 TextArea 设置样式。而在 Flex 4 中,使用 Halo 或 Spark 的 TextArea 只能通过设置 contentBackgroundColor 或更改它的 BorderSkin 设置样式。
快速调节整个应用程序的可视外观最简单的方法是通过一个分阶段流程设计它的外观。按照此步骤设计应用程序的正常外观:
mx:Style 标签开始。Button.loginButton。正确设置组件的样式后,创建一个 Normal.css 样式表,并使用 mx:Style 标签的 source 属性将您的 WindowedApplication 组件(或子组件)与它链接起来。将所有 mx:Style 声明剪切并粘贴到 Normal.css。
定义 Normal.css 样式表后,您可以创建一个高对比度版本:
从正常的样式表入手可以防止重复工作。
您可以在运行时使用 StyleManager 类更改样式。
当用户要载入 HighContrast.css 声明时,调用:
StyleManager.loadStyleDeclarations("assets/HighContrast.swf",true);
要卸载这些声明并还原为 Normal.css 样式表(根组件中链接的那个),可使用以下代码:
StyleManager.unloadStyleDeclarations("assets/HighContrast.swf",true);
StyleManager 将自动刷新您的 AIR 应用程序以显示更改情况。由于设置组件样式是应用程序开发的一个常见部分,只要额外多花点功夫就可以创建出高对比度模式。这是一个宝贵的功能,它使更多用户能用上您的应用程序。
有关组件特定实施的更多详细信息,请参考 HighContrast.css 和 Normal.css (它们在范例应用程序的 assets 文件夹中)。
AIR 2 辅助功能框架中的主要功能是允许 JAWS 等屏幕阅读器连接到您的应用程序。借助 JAWS,失明用户以及有视觉障碍的用户可以借助软件读出 AIR 应用程序的内容。以下是一些需要谨记的设计注意事项,它们可以帮助您的用户充分利用这个新功能:
tabIndex 属性的组件开始,按您指定的顺序读取它们。读取这些组件后,屏幕阅读器将按剩余组件在源代码中的出现顺序读取它们。用户始终可以中断这个过程。可以覆盖 AIR 自动显示给屏幕阅读器的文本。对于自定义组件,您可以通过创建和指定一个 AccessibilityProperties object 控制读取内容。例如,以下代码将“自定义组件”设置为要读取的文本:
myComponent.accessibilityProperties = new AccessibilityProperties;
myComponent.accessibilityProperties.name = "Custom component";
// other application logic ...
Accessibility.updateProperties();
对 updateProperties() 的调用将扫描应用程序中新的或更改的 AccessibilityProperties 对象;当您创建或更改任何 AccessibilityProperties 对象后,需要这个调用。不必在每次更改后调用 updateProperties();应当像提交调用那样在完成对所有 AccessibilityProperties 对象的更改后使用它。它是一个同步函数,执行可能需要几毫秒。如果在不必要的情况下调用它,会导致性能下降。
表单容器以一种对于用户而言符合逻辑、熟悉的可视方式呈现说明和控制。屏幕阅读器以一种同样熟悉、直观的方式,使用 Form 和 FormItem 容器读出内容。在 AIR 2 中,现有 Form 容器无需修改就可以与屏幕阅读器一起使用。考虑以下登录表单:
<mx:Form width="100%" height="100%">
<mx:FormHeading label="Enter your username and password."/>
<mx:FormItem label="Username">
<mx:TextInput id="usernameTextInput" tabIndex="3"/>
</mx:FormItem>
<mx:FormItem label="Password">
<mx:TextInput id="passwordTextInput" displayAsPassword="true" tabIndex="4"/>
</mx:FormItem>
</mx:Form>
当用户跳至 usernameTextInput 时,会读出“用户名”,当用户跳至 passwordTextInput 时,会读出“密码”。注意,当用户跳至 TextInput 对象时,屏幕阅读器不会读出 FormHeading 文本。FormHeading 文本像 Label 一样呈现,但用户可以指示屏幕阅读器导航到它。
至于程序的其他方面,您应当充分利用 Flex 容器(如 Form 和 FormItem)像屏幕阅读器呈现您的 UI 结构。
有些情况下,您可能希望在未使用屏幕阅读器时使用自定义组件,但在使用屏幕阅读器时恢复为可访问组件。或者,您可能希望在连接了屏幕阅读器时通过打开浏览器显示 HTML,但在未连接时使用 HTML 组件。
要检查是否已连接屏幕阅读器很简单。以下是范例应用程序的 List 控件的示例 keyDown 处理函数:
protected function urlList_keyDownHandler(event:KeyboardEvent):void
{
if ( event.keyCode == Keyboard.ENTER )
{
if ( Accessibility.active )
{
// A screen reader is attached. Use the browser instead of the HTML component
openURL();
}
else
{
htmlView.location = urlList.selectedItem as String;
// ...
}
}
}
Accessibility 类未提供任何可绑定值,因此您不能使用 Flex ChangeWatcher 对象或数据绑定。我发现在我的 Model 中创建一个 Bindable 变量很实用,它在我的应用程序完成初始化两秒后被赋予值 Accessibility.active。(屏幕阅读器与 AIR 之间的连接不是即时的,不幸的是,这导致在一个很短的时间段内,Accessibility.active 属性的准确性不确定。)虽然这个变量在应用程序初始化后不反映屏幕阅读器的连接或断开连接情况,我们说到了最典型的用例(一名有视觉障碍的用户使用运行中的屏幕阅读器启动我的应用程序)。
将 AIR 2 应用程序变为可访问是一个涉及多步的过程,并且其中最重要的部分是整个开发过程中的设计注意事项。主要步骤如下:
--accessible 命令行选项传递给 Flex 编译器,或选择“生成可访问的 SWF 文件”编译器选项,启用辅助功能。tabIndex。如果要为组件禁用跳位焦点,将 tabEnabled 设置为 false。accessibilityProperties.name,更改屏幕阅读器为您的组件读取的文本。Accessibility.active 以确定用户是否正在使用屏幕阅读器。与所有可用性改进一样,修改辅助功能几乎可以无限继续下去。当然在实际操作中会遇到限制,但我相信我在本文中罗列出的提示不仅易于实施,而且对于用户而言是难能可贵的。通过鼓励有视觉障碍的用户尝试您的程序并给予反馈,从而更好地理解可能需要进一步改进的地方。(您可能还希望连接一个屏幕阅读器、关闭显示器、自己尝试在没有可视提示的情况下使用应用程序。)
然后决定修改现有组件以添加缺少的功能。例如,我通过修改一个 HTML 组件,允许用户使用方向键在锚点标签之间导航。对于这类修改,您必须从实际使用这些功能的用户那里获取反馈。修改和创建新组件是一个复杂的过程;有关更多信息,请观看 Adobe MAX 2009 会话构建可访问的 Flex 和 Adobe AIR 应用程序。
Adobe AIR Forums |
More |
| 04/11/2012 | Surround sound 5.1 with Air 3.2 on desktop app? |
|---|---|
| 12/12/2011 | Live Streaming H.264 Video on iOS using AIR |
| 04/17/2012 | HTMLLoader - Google Maps? |
| 04/12/2012 | Tabindex in forms on mobile? |
Adobe AIR Blog |
More |