Flutter: Semantics控件

很久没有更新Flutter文章了,今天的文章主要是介绍一下Semantics
我们的交流QQ群:892398530。
转载请标注原文出处

本来为译文,原文请戳这里
这篇文章解释了Flutter中Semantics的概念。
难度:入门级。
前言
如果你读过有关于Flutter的代码,那么你有时候你会注意到Semantics或者SemanticsConfiguration,但官方文档却对这个很有趣的话题却没有很多的资料。
这篇文章是对这个话题的介绍。与此同时也会向您展示您的应用是否会考虑使用Semantics,这取决于其重要性和兴趣度。
简而言之-这是什么东东?
官方文档Semantics类介绍如下:

一个用来描述控件树中控件含义的控件,这些描述被可访问性工具,搜索引擎或者其他其他语义分析软件使用,以确定应用程序的含义。

我个人认为这段解释云里雾里。 所以用我自己的话说就是:

言简意骇,Semantics的概念是:

  1. 完全可选(这意味着你可以完全不关心这个控件,但这并不推荐),
    2.意味着可以与Android TalkBackiOS VoiceOver一起使用(例如主要由视障人士使用),

3.意味着可以由屏幕阅读器(Screen Reader)使用,它会描述应用程序而无需查看屏幕。

通过阅读本文,我们可以意识到,如果您将应用程序定位为视障人士也可以使用,这将是多么重要...

在Flutter中他是怎么实现的?
当Flutter渲染控件树时,它还会维护第二个控件树,称为Semantics Tree,它被移动设备辅助技术(Android TalkBackiOS VoiceOver)所使用。

Semantics树的每个节点都是SemanticsNode,它可能对应于一个或一组Widgets。

每个SemanticsNode都会对应一个SemanticsConfiguration,这是一组属性,这将告诉移动设备辅助技术如何:

  1. 描述节点
  2. 与节点一起行动

SemanticsConfiguration
描述与之相关的SemanticsNode的语义信息。下面将列举其中部分属性(更详细的请查阅官方文档)。
| 名称| 描述|
| ------------- |:-------------:|
| decreasedValue | 一个执行decrease动作的返回值,如Slider |
| increasedValue | 一个执行increased动作的返回值,如Slider|
| isButton | 该节点是否是Button|
| isChecked |该节点是一种 CheckBox,是否被选中|
| isEnabled | 该节点是否可用|
| isFocused |该节点是否持有用户的焦点|
| isHeader | 该节点是否为Header |
| isSelected |该节点是否被选中 |
| isTextField |该节点是否文本字段|
| hint |在此节点上执行操作的结果的简要说明|
| label |节点描述|
| value |对值的文字性描述|

** 具有语义的隐式Flutter控件

大多数Flutter控件被隐式定义为Semantics,因为它们可能被Screen Reader引擎直接地或间接地使用。
为了解释一下这段话,下面是从Flutter源代码摘取的与Button相关代码:

class _RawMaterialButtonState extends State<RawMaterialButton> {

  ...

  @override
  Widget build(BuildContext context) {

    ...

    return new Semantics(
      container: true,
      button: true,
      enabled: widget.enabled,
      child: new ConstrainedBox(
        constraints: widget.constraints,
        child: new Material(
          ...
        ),
      ),
    );
  }
}

如何定义Semantics

有时候定义屏幕的一部分以便可以通过移动设备辅助技术进行描述可能会很有趣。
这种情况下,只需要使用下面的控件做包裹子控件的容器就可以了:

  • Semantics,当你只想描述一个特定的控件
  • MergeSemantics,当你想描述一组控件。这种情况下,被定义在该子节点下的子控件树中的不同的Semantics会被整合到一个单独的Semantics中。这对于重新组合语义非常有用,但是,如果语义冲突,结果可能是无意义的。

单一Semantics
用于定义语义的类是Semantics。 这个类有2个构造函数:一个是冗长的,一个是简洁的。
下面是定义Semantics的两种方法,解释如下:

@override
Widget build(BuildContext context){
  bool toBeMergedWithAncestors = false;
  bool allowDescendantsToAddSemantics = false;

  return new Semantics(
    container: toBeMergedWithAncestors,
    explicitChildNodes: allowDescendantsToAddSemantics,
    ...(list of all properties)...

    child: ...
  );
}

@override
Widget build(BuildContext context){
  SemanticsProperties properties = new SemanticsProperties(...);
  bool isContainer = toBeMergedWithAncestors;
  bool explicitChildNodes = allowDescendantsToAddSemantics;

  return new Semantics.fromProperties(
    container: isContainer,
    explicitChildNodes: explicitChildNodes,
    properties: properties,
    child: ...
  );
}

| 名称| 缺省值|描述|
| ------------- |:-------------:| :-------------:|
| container | false |如果值为true,则会将新的SemanticsNode添加到Semantics树中,从而不允许此Semantics与父Semantics合并。 如果值为false,则此语义将与父Semantics合并 |
| explicitChildNodes | false| 该控件的子控件是否允许将Semantics信息添加到该控件的SemanticsNode中 |

如何不使用Semantics

有时时候可能会出现根本不需要任何Semantics的情况。 这可能是屏幕的一些部分,它们只是装饰性的,对用户来说并不重要。
这种情况下,您需要使用ExcludeSemantics来去除某个控件及其子控件的Semantics。语法如下:

@override
Widget build(BuildContext context){
  bool alsoExcludeThisWidget = true;

  return new ExcludeSemantics(
    excluding: alsoExcludeThisWidget,
    child: ...
  );
}

exclude属性(默认值:true)告诉系统您是否也希望从Semantics树中排除此Widget。

如何将控件重组成一个Semantics?
在某些情况下,您可能还想重新组合一组控件的所有Semantics。
这种情况的一个基本示例可能是由Label和Checkbox组成的可视块,每个都定义了自己的Semantics。 最好的是,如果用户按下该块,则移动设备辅助技术将提供与该组相关的辅助功,而不是提供该组的每个Widget的辅助信息。

这种情况下,你应该使用MergeSemantics

注意

当你想要合并Semantics时要非常小心,因为如果你有任何冲突的Semantics,这对用户来可能说是荒谬的。 例如,如果您有一个由多个复选框组成的块,每个复选框具有不同的状态(已选中且未选中),则将检查生成的语义状态,从而误导用户。

如何调试Semantics
最后,如果要调试app中的Semantics,可以将MaterialApp的showSemanticsDebugger属性设置为true。 这将强制Flutter生成叠加层以可视化语义树。

void main(){
  runApp(new MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: new Text('My Semantics Test Application'),
      showSemanticsDebugger: true,
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new FirstScreen(),
    );
  }
}

** 结论
由于官方文档在这方面的介绍并不是很详细,我只想与您分享我的理解。

我希望这一介绍突出了这样一个事实,即如果你想有一天发布一个应用程序,考虑语义是很重要的,因为移动用户可能会打开手机的移动设备辅助技术并使用你的应用程序。 如果您的应用程序尚未准备好使用此技术,则可能存在无法使用的风险。
我希望通过本文可以让您意识到如果有一天您想发布一个app,考虑使用Semantics是很重要的,因为手机用户可能打开移动设备辅助技术并使用你的app。 如果您的app尚未准备好使用此技术,则可能存在无法使用的风险。

开心写代码~

上一篇:Flutter: Failed to notify project evalution listener


下一篇:Flutter环境问题:Requested 'libusbmuxd >= 1.1.0' but version of libusbmuxd is 1.0.10