跳转至正文

Web 无障碍

关于 Web 无障碍的信息

背景

#

Flutter 通过将内部 Semantics 树转换为屏幕阅读器可理解的 HTML DOM 结构来支持 Web 无障碍。由于 Flutter 在单一 canvas 上渲染 UI,需要特殊层向 Web 浏览器暴露 UI 的含义与结构。

可选启用的 Web 无障碍

#

隐形按钮

#

出于性能考虑,Flutter 的 Web 无障碍默认未开启。要启用无障碍,用户需按下带有 aria-label="Enable accessibility" 的隐形按钮。按下后,DOM 树将反映 widget 的全部无障碍信息。

在代码中开启无障碍模式

#

另一种方式是在运行应用时添加以下代码以开启无障碍模式。

dart
import 'package:flutter/semantics.dart';

void main() {
  runApp(const MyApp());
  if (kIsWeb) {
    SemanticsBinding.instance.ensureSemantics();
  }
}

使用语义角色增强无障碍

#

什么是语义角色?

#

语义角色定义 UI 元素的用途,帮助屏幕阅读器和其他辅助工具向用户有效解释和呈现你的应用。例如,角色可表明 widget 是按钮、链接、标题、滑块还是表格的一部分。

Flutter 的标准 widget 通常会自动提供这些语义,但没有明确定义角色的自定义 widget 可能对屏幕阅读器用户难以理解。

通过分配合适的角色,你可以确保:

  • Screen readers can announce the type and purpose of elements correctly.

  • Users can navigate your application more effectively using assistive technologies.

  • Your application adheres to web accessibility standards, improving usability.

  • 屏幕阅读器能正确播报元素的类型与用途。

  • 用户能借助辅助技术更高效地在应用中导航。

  • 应用符合 Web 无障碍标准,提升可用性。

在 Flutter Web 中使用 SemanticsRole

#

Flutter 提供 Semantics widgetSemanticsRole enum,让开发者为 widget 分配特定角色。Flutter Web 应用渲染时,这些 Flutter 特有角色会转换为网页 HTML 结构中对应的 ARIA 角色。

1. 标准 widget 的自动语义

许多标准 Flutter widget(如 TabBarMenuAnchorTable)会自动包含语义信息及其角色。尽可能优先使用这些标准 widget,它们开箱即用地处理许多无障碍方面。

2. 显式添加或覆盖角色

对于自定义 widget 或默认语义不足时,使用 Semantics widget 定义角色:

以下示例演示如何显式定义列表及其列表项:

dart
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';


class MyCustomListWidget extends StatelessWidget {
  const MyCustomListWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // This example shows how to explicitly assign list and listitem roles
    // when building a custom list structure.
    return Semantics(
      role: SemanticsRole.list,
      explicitChildNodes: true,
      child: Column(
        children: <Widget>[
          Semantics(
            role: SemanticsRole.listItem,
            child: const Padding(
              padding: EdgeInsets.all(8.0),
              child: Text('Content of the first custom list item.'),
            ),
          ),
          Semantics(
            role: SemanticsRole.listItem,
            child: const Padding(
              padding: EdgeInsets.all(8.0),
              child: Text('Content of the second custom list item.'),
            ),
          ),
        ],
      ),
    );
  }
}