为了账号安全,请及时绑定邮箱和手机立即绑定

用Flutter开发无障碍移动应用

标签:
移动开发

照片由 Christina Morillo 拍摄,来自 Pexels

可访问性是现代应用程序开发中的一个重要方面。在本文中,你会发现可访问性不仅能满足法律要求,还能改善所有用户的用户体验。我们将从包容性应用的背景以及其深远影响出发。然后,我们将展示在使用Flutter开发应用时哪些领域尤其重要。我们将重点关注三个方面:文本大小调整、屏幕阅读器支持和键盘操作。

背景及其影响:

在这个日益数字化的世界里,确保所有人的移动应用程序都能访问到至关重要——不仅因为法律要求,还为了扩大用户群。所有人都应该能够无障碍访问数字内容,无论他们的能力如何。

在美国,例如《美国残疾人法案(ADA)》之类的法律强调了网站和网络应用程序的无障碍性。《ADA》具体指出:

网站和网络应用被定义为公共场所,特别是提供如联邦网站等公共服务的网站。所有公共部门和商业机构都需要根据WCAG 2.0的要求,提供平等的网页访问。

ADA

所以,公开网站必须按照[WCAG 2.0(Web内容可访问性指南)]的标准设计,确保所有用户群体能够访问。

在欧洲,数字应用程序的可访问性也越来越受到重视。自2025年起,例如,《欧洲无障碍法案》(EAA)将要求所有旅行、银行和电子商务应用程序都必须完全无障碍(来源:mobilea11y)。

在德国,_无障碍信息技术条例 (BITV 2.0)_ 规定了实施_WCAG 2.0_标准,以确保数字服务的无障碍。[BITV 2.0]几乎与_WCAG 2.0_的标准相同。

可访问的应用程序不会排除任何人,并为所有用户提供一个舒适的用户体验,无论他们的能力如何。这些指南的实施使应用程序完全可访问,这不仅符合法律规定的要求,还能扩大应用程序的用户群体。因此,让我们在开发应用程序时坚定地贯彻“可访问性优先”的原则。

房事无障碍话题

可访问性是移动应用开发中涵盖多个方面的主题,包含许多子话题。在这篇文章中,我们概述了最重要的可访问性准则,并特别关注与使用Flutter开发相关的要点。

  • 文本缩放功能
  • 屏幕阅读器支持
  • 键盘操作性
  • 触摸目标大小
  • 对比度比率
  • 布局一致性
  • 颜色使用规范
  • 简化数据输入(例如自动填充)
  • 缩放和放大功能
  • 手势和触控操作
  • 字幕和文字记录功能

这些主题中的某些方面,如触控区域的大小、对比度、一致的布局和色彩使用,主要由UX设计师负责。其他方面,如简化数据录入、缩放和放大功能、手势操作、字幕和转录文本,则部分由操作系统或相关软件包的支持。

有三个领域特别具有挑战性和复杂性,使用 Flutter 进行开发:

  1. 文本缩放: 确保文本根据用户设置自动调整大小,保持可读性。
  2. 支持屏幕阅读器: 优化应用以支持iOS的VoiceOver和Android的TalkBack屏幕阅读器。
  3. 键盘操作: 支持键盘导航,特别是针对表单和互动元素。

这些方面在现有应用中追加实现可访问性功能时可能特别具有挑战性。如果已使用的库或包不符合可访问性指南,会显著增加工作量。设计不良的功能还可能影响应用的可用性和可访问性。
为了尽量减少开发工作量并避免以后的调整,也应该坚持“可访问性优先”的基本原则。尽早在开发过程中整合可访问性标准可以节省时间,减少不必要的额外工作量,并显著提升用户体验。

文本大小调整

在调整字体大小时,需要注意的是,Android 和 iOS 系统提供不同的设置。在 Android 上,设备的字体大小可以配置到最高 200%。(来源:Android 开发者)而在 iOS 上则可以调整到最高 310%。(来源:StackOverflow)这些设置可以随时在应用运行时进行更改,因此我们的应用也必须在运行时响应这些变化。Flutter 应用默认会这样做,但我们的布局也必须同样具有响应性,以避免出现类似下图所示的渲染溢出错误。

调整字体大小从100%增大到200%

文本折行

Text 小部件自带一些有助于解决此类问题的属性:maxLinesoverflowsoftWraptextScaler,因此最好熟悉这些属性。可以在 ColumnRow 中使用 FlexibleExpanded 小部件来包裹 Text 小部件以解决文字缩放问题。以下代码示例展示了如何避免溢出错误。结果请参见下方的图片。

    // 会导致溢出错误  
    Row(  
      children: [  
        Text(  
          '这是相当长的一段描述,包含很多文字',  
          softWrap: true,  
          maxLines: 2,  
        ),  
      ],  
    )  

    // 允许文本换行  
    Row(  
      children: [  
        Flexible(  
          child: Text(  
            '这是相当长的一段描述,包含很多文字',  
            maxLines: 2,  
          ),  
        ),  
      ],  
    )

需要注意的是,使用 Flexible 并不一定非得定义 softWrap

没有使用 Flexible 小组件的文本和位于 Flexible 小组件内的文本

连字符

连字符对于长时间文本来说,在更大规模下迅速引起困难时确实是个救命稻草。虽然 Flutter 自身没有内置连字符功能,但有两个可以使用的包:auto_hyphenating_texthyphenatorx。到目前为止,我们发现 auto_hyphenating_text 包的体验最好。通过巧妙组合这些功能,每个文本字段都可以做到完全可访问。在下面的例子中,你可以看到使用和不使用连字符的区别。

    // 无连字符  
    Row(  
      children: [  
        Flexible(  
          child: Text(  
            'Flutter 是一个很好的框架,用于学习无障碍性!',  
            softWrap: true,  
          ),  
        ),  
      ],  
    )  

    // 带连字符  
    Row(  
      children: [  
        Flexible(  
          child: AutoHyphenatingText(  
            'Flutter 是一个很好的框架,用于学习无障碍性,支持自动断字。',  
            softWrap: true,  
          ),  
        ),  
      ],  
    )

没有连字符的比较文本和有连字符的比较文本

图标大小调整

如果用户在阅读文本时遇到问题,他们也可能在小图标上遇到问题。在这种情况下,均匀放大或缩小文本和图标是有道理的,因为许多移动应用程序使用交互图标。当前的文本缩放因子可以通过使用 MediaQuery 获得。

final textScale = 定义为 MediaQuery.of(context).textScaler.scale(1);

你可以设置最小和最大图标大小,并使用 textScale 参数来计算调整后的大小。你可以使用 clamp 为你的图标大小设定上限和下限,如下代码所示:

    // 没有图标缩放  
    const double iconSize = 16.0;  // 这里我们定义了图标的基本大小  

      // ...  

    GestureDetector(  
      onTap: () {},  
      child: Row(  
        children: [  
          const Text('图标按钮'),  // 显示图标按钮文本  
          const SizedBox(width: 8),  
          Icon(  
            Icons.info,  
            size: iconSize,  // 显示一个信息图标  
          ),  
        ],  
      ),  
    )  

    // 有图标大小调整  

    final double textScale = MediaQuery.textScalerOf(context).scale(1);  // 获取文本缩放比例  
    const double iconSize = 16.0;  // 这里我们定义了图标的基本大小  
    const double minIconSize = 12.0;  
    const double maxIconSize = 48.0;  
    final double scaledIconSize = iconSize * textScale;  // 根据文本缩放比例调整图标大小  

      // ...  

    GestureDetector(  
      onTap: () {},  
      child: Row(  
        children: [  
          const Text('图标按钮'),  // 显示图标按钮文本  
          const SizedBox(width: 8),  
          Icon(  
            Icons.info,  
            size: scaledIconSize.clamp(minIconSize, maxIconSize),  // 限制图标大小在最小和最大值之间  
          ),  
        ],  
      ),  
    )

无图标大小调整和有图标大小调整

屏幕阅读器兼容性

无论是 Android 还是 iOS 都提供了自己的屏幕阅读器:Android 使用 TalkBack,而 iOS 则使用 VoiceOver。这些屏幕阅读器高度可自定义,提供了许多配置选项。例如,可以指定读出元素角色、值和标签等信息的顺序。还可以设置文本和图像的自动读取功能。要开发出无障碍的应用程序,了解这些设置非常重要。唯有如此,才能确保您的应用程序对使用屏幕阅读器的用户来说具备最佳的无障碍体验。

但是屏幕阅读器是如何与Flutter配合工作的呢?在Flutter的渲染管道的合成阶段之后,会使用诸如Text、Image或Button等widget来构建一个语义元素的树形结构,即所谓的语义树。Flutter使用这棵树来与屏幕阅读器交互。树中的每个节点都包含元数据,例如元素的描述、动作和角色。若要查看语义树,可以调用debugDumpSemanticsTree。下面的图片显示了左上角的代码片段和右上角的结果屏幕,以及在控制台上输出的语义树。

Flutter 提供了 Semantics 小部件来添加你自己的节点到这个树中。这使开发人员能够更好地控制与屏幕阅读器的交互。Semantics 小部件最重要的属性是 labelvalue,以及如果是适用的,角色。
但是为什么 labelvalue 最为重要?想象有两个 Text 组件,一个作为标签,另一个显示相应的值。如果没有这样的语义关系,视力障碍者就无法理解这两个文本框之间的联系。此外,屏幕阅读器让用户可以自行设定标签和值的读取顺序。

buttonimageslidertextField 这类属性可用于设置角色。对于复选框,其状态可以通过设置 checked 属性为 truefalse 来传递。屏幕阅读器将会正确读出复选框的状态。除了 checked,还可以选择 toggledmixed,但这些选项是互斥的。(来源:Flutter API

excludeSemantics 属性可以用来从语义树中排除某些元素。这允许你专门从子组件中排除特定的语义节点。或者,你可以使用 ExcludeSemantics 小部件来跳过整个子树。这特别有用,可以排除那些没有提供额外信息或与使用应用无关的装饰性元素,例如图片。以下代码片段展示了 Semantics 小部件的几个应用场景。

    // 自定义按钮示例  
    const label = '显示详情';  

    Semantics(  
      label: label,  
      button: true,  
      child: GestureDetector(  
        onTap: () {},  
        child: const Row(  
          children: [  
            Text(label),  
            SizedBox(width: 8),  
            Icon(Icons.info, size: 16),  
          ],  
        ),  
      ),  
    )  

    // 带标签值的示例  
    const label = '终极答案';  
    const value = '42';  

    Semantics(  
      label: label,  
      value: value,  
      excludeSemantics: true, // 排除子组件的语义信息  
      child: const Column(  
        children: [  
          Text(label),  
          Text(value),  
        ],  
      ),  
    )  

    // ExcludeSemantics 示例  
    const ExcludeSemantics(  
      child: Icon(Icons.info, size: 16),  
    )

Flutter 中的许多小部件已经包含处理语义的 semanticsLabeltooltip 属性。在这种情况下,就不需要使用 Semantics 小部件了。例如,Text 小部件提供了一个 semanticsLabel 属性,这对于向屏幕阅读器传达清晰信息,而非仅仅符号或占位符,很有用。假设你在某个视图中有多个值,有些地方用“-”表示值缺失。屏幕阅读器会将“-”读作“减号”,这可能不是你想要的信息,你可以使用 semanticsLabel 让屏幕阅读器提供更多的上下文信息,例如宣布:“不可用”。例如,你可以这样设置:

    // 可访问文本标签示例  
    Text('-', semanticsLabel: '暂无',)
键盘易用性

随着越来越多的用户也开始使用外部键盘操作智能手机,应用程序也必须适应键盘用户。就像在使用电脑时一样,也可以使用 Tab 键(以及 Shift+Tab)在移动应用的互动元素之间导航。Flutter 提供了 Focus 小部件以实现这一功能。

Focus 组件管理一个 FocusNode,并且 FocusNode 也以树状结构组织,类似于语义树。类似于 debugDumpSemanticsTree,你可以使用 debugDumpFocusTree 来查看 焦点树。

Flutter 提供了多种接口来管理焦点。

// 代码保持不变

删除了“代码保持不变”的注释,因为源文本中没有该内容。

  • **Focus****FocusNode**:控制应用内的焦点。
  • **FocusScope**:界定焦点变化的特定区域。
  • **FocusManager**:管理主要焦点及节点的顺序。
  • **FocusTraversalGroup**:允许用户自定义焦点变化顺序。
  • **FocusTraversalOrder**:描述FocusTraversalGroup内节点顺序。
  • **ExcludeFocus**:排除焦点树的一部分,就像ExcludeSemantics一样。

通过巧妙地结合这些组件和类,可以灵活地设计和调整焦点顺序,以确保顺畅的导航。以下代码展示了如何结合使用FocusTraversalGroupFocusTraversalOrder来更改焦点顺序。使用NumericFocusOrder来设置所需的遍历顺序。

    FocusTraversalGroup(
      policy: OrderedTraversalPolicy(),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          FocusTraversalOrder(
            order: const NumericFocusOrder(2),
            child: TextButton(
              onPressed: () {},
              child: const Text('按钮2'),
            ),
          ),
          FocusTraversalOrder(
            order: const NumericFocusOrder(1),
            child: TextButton(
              onPressed: () {},
              child: const Text('按钮1'),
            ),
          ),
          FocusTraversalOrder(
            order: const NumericFocusOrder(3),
            child: TextButton(
              onPressed: () {},
              child: const Text('按钮3'),
            ),
          ),
        ],
      ),
    )

特别重要的是要避免所谓的键盘陷阱。你的应用中的每一页和每个功能都必须完全可以通过键盘访问。不能让用户陷入无法继续导航的困境。复杂的操作手势,比如在滚动视图中的滑动操作,也应该有相应的替代按钮,比如在详情页面中。

结论啦

总之,在移动应用开发中,可访问性是一个至关重要的主题。一个可访问的应用不仅满足法律要求,还能显著改善所有用户的体验。尽管集成诸如文本缩放、屏幕阅读器支持和键盘操作等功能需要时间和大量测试,这些功能为打造一个更包容和可访问的应用所带来的好处显然超过了额外的工作和设计妥协。通过采取“可访问性优先”的方法,移动应用可以扩大目标受众,同时创造一个更友好的用户体验,让每个人都能参与其中。这将使所有用户在长期内获得更大的成功和满意度。因此,让我们一起使这个世界更可访问,更美好!(由Jonas Klock提供)

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消