原文链接:https://www.didierboelens.com/2018/07/semantics/
这篇文章主要用来解释 Semantics 组件的作用,是译文 内容非原创
前言
如果你看过一些有关于Flutter的内容,你有时注意到使用了 Semantics 或者 SemanticsConfiguration 组件,但是在官方文档中有关这些内容的叙述并不多。本文是对这个主题的介绍,并且展示考虑在您的 APP 中使用这类组件的重要性和一些有趣的内容。
简而言之——这是什么?
在官方文档中是这样介绍 Semantics 类的:
A widget that annotates the widget tree with a description of the meaning of the widgets Used by accessibility tools, search engines, and other semantic analysis software to determine the meaning of the application.
通过对控件含义的描述信息注释控件树的一个控件,可通过可访问性工具,搜索引擎和其他语义化分析的软件确定应用程序的含义。
简而言之:
- 可选择性。这意味着你可以完全不关心它的使用,但是不建议
- 主要用于在 Android TalkBack 或者 iOS VoiceOver 使用,例如视障人士的使用
- 主要用于屏幕阅读器的使用,它将描述这个应用程序而不需要查看屏幕
阅读到这里,如果你的应用程序主要用于视障人士,这是多么的重要。
如何在 Flutter 中使用
当在 Flutter 中渲染 Widget 树时,它还会维护第二个树,称为 Semantics Tree。它通常被用在移动辅助设备上,例如Android TalkBack 或者 iOS VoiceOver 。
Semantics Tree的每一个节点都是 SemanticsNode,它可能对应一个或者一组Widgets。
每个 SemanticsNode 都链接到一个 SemanticsConfiguration, 通过一系列的属性告诉移动辅助设备: 如何描述节点、 如何配合节点的工作。
SemanticsConfiguration
描述与之相关联的 SemanticsConfiguration 的语义信息,一下是部分属性(详情参阅 官方文档)
名称 | 描述 |
---|---|
decreasedValue | 执行减少操作所产生的值(例如滑块) |
increasedValue | 执行增加操作所产生的值(例如滑块) |
isButton | 这个节点是不是按钮 |
isChecked | 是否是复选框,是否被选中 |
isEnabled | 是否允许使用 |
isFocused | 是否获得到焦点 |
isHeader | 是否是Header节点 |
isSelected | 是否是选择节点 |
isTextField | 是否是text field节点 |
hint | 对在此节点上执行操作的结果的简述 |
label | 节点的描述 |
value | 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。这个类有2个构造函数:一个是冗长的,一个是简洁的。
下面是定义语义的两种方法,解释如下:
@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,从而使该语义不会与祖先的语义合并。如果值为假,则此语义将与祖先的语义合并 |
explicitChildNodes | false | 表明是否允许此小部件的后代向该小部件的SemanticsNode添加语义信息 |
如何没有语义?
有时候,屏幕上只具有装饰性、对用户不重要的部分,您可能根本不需要任何语义。
在这种情况下,您需要使用 ExcludeSemantics 类来移除这个小部件的所有后代的语义。其语法如下:
@override
Widget build(BuildContext context){
bool alsoExcludeThisWidget = true;
return new ExcludeSemantics(
excluding: alsoExcludeThisWidget,
child: ...
);
}
exclude属性(默认值:true)告诉系统您是否也希望这个小部件被排除在语义树之外。
如何将小部件语义重组成单一语义?
在某些情况下,您可能还希望重组一组小部件的所有语义成单一语义。
例如,在 一个由 label 和 checkbox(复选框) 组成的 功能块中,且每个复选框定义自己的语义的情况下,如果用户按下该块,移动设备辅助技术将提供与整个功能块相关的帮助信息,而不是功能块中的每个小部件的信息。
在这种情况下你应该使用 MergeSemantics 类.
WARNING
Be very careful when you want to merge the Semantics since if you have any conflicting Semantics, this might result in becoming nonsensical for the user. For example, if you have a block made up of several checkboxes, each of them having different statuses (checked and not checked), the resulting Semantics status will be checked, misleading the user.
当您想合并语义时要非常小心,因为如果您有任何冲突的语义,这可能会导致语义对用户来说变得毫无意义。例如,如果您有一个由几个复选框组成的块,每个复选框都有不同的状态(选中和未选中),那么结果的语义状态将被选中,从而误导用户。
如何调试语义?
最后,如果希望调试应用程序的语义,可以将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(),
);
}
}
总结
如果您希望某天发布应用程序,那么考虑语义非常重要,因为移动用户可能会打开他们的手机的移动设备辅助技术并使用您的应用程序。如果您的应用程序还没有为这项技术做好准备,则可能存在无法使用该技术的风险。