Add SemanticsNode.isSelected flag (#10610)
* Add SemanticsNode.isSelected flag * Adds example usage to TabBar See also https://github.com/flutter/engine/pull/3764 * Review comments * whitespace fixes * Fix doc ref and update engine roll
This commit is contained in:
parent
1f4f75bb50
commit
0774c5194b
@ -730,9 +730,14 @@ class _TabBarState extends State<TabBar> {
|
||||
// then give all of the tabs equal flexibility so that their widths
|
||||
// reflect the intrinsic width of their labels.
|
||||
for (int index = 0; index < widget.tabs.length; index++) {
|
||||
wrappedTabs[index] = new InkWell(
|
||||
onTap: () { _handleTap(index); },
|
||||
child: wrappedTabs[index],
|
||||
wrappedTabs[index] = new MergeSemantics(
|
||||
child: new Semantics(
|
||||
selected: index == _currentIndex,
|
||||
child: new InkWell(
|
||||
onTap: () { _handleTap(index); },
|
||||
child: wrappedTabs[index],
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!widget.isScrollable)
|
||||
wrappedTabs[index] = new Expanded(child: wrappedTabs[index]);
|
||||
|
@ -2873,10 +2873,12 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
RenderBox child,
|
||||
bool container: false,
|
||||
bool checked,
|
||||
String label
|
||||
bool selected,
|
||||
String label,
|
||||
}) : assert(container != null),
|
||||
_container = container,
|
||||
_checked = checked,
|
||||
_selected = selected,
|
||||
_label = label,
|
||||
super(child);
|
||||
|
||||
@ -2900,8 +2902,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
/// If non-null, sets the "hasCheckedState" semantic to true and the
|
||||
/// "isChecked" semantic to the given value.
|
||||
/// If non-null, sets the [SemanticsNode.hasCheckedState] semantic to true and
|
||||
/// the [SemanticsNode.isChecked] semantic to the given value.
|
||||
bool get checked => _checked;
|
||||
bool _checked;
|
||||
set checked(bool value) {
|
||||
@ -2912,7 +2914,19 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
markNeedsSemanticsUpdate(onlyChanges: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the "label" semantic to the given value.
|
||||
/// If non-null, sets the [SemanticsNode.isSelected] semantic to the given
|
||||
/// value.
|
||||
bool get selected => _selected;
|
||||
bool _selected;
|
||||
set selected(bool value) {
|
||||
if (selected == value)
|
||||
return;
|
||||
final bool hadValue = selected != null;
|
||||
_selected = value;
|
||||
markNeedsSemanticsUpdate(onlyChanges: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.label] semantic to the given value.
|
||||
String get label => _label;
|
||||
String _label;
|
||||
set label(String value) {
|
||||
@ -2927,7 +2941,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
bool get isSemanticBoundary => container;
|
||||
|
||||
@override
|
||||
SemanticsAnnotator get semanticsAnnotator => checked != null || label != null ? _annotate : null;
|
||||
SemanticsAnnotator get semanticsAnnotator => checked != null || selected != null || label != null ? _annotate : null;
|
||||
|
||||
void _annotate(SemanticsNode node) {
|
||||
if (checked != null) {
|
||||
@ -2935,6 +2949,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
||||
..hasCheckedState = true
|
||||
..isChecked = checked;
|
||||
}
|
||||
if (selected != null)
|
||||
node.isSelected = selected;
|
||||
if (label != null)
|
||||
node.label = label;
|
||||
}
|
||||
|
@ -292,6 +292,10 @@ class SemanticsNode extends AbstractNode {
|
||||
bool get isChecked => (_flags & SemanticsFlags.isChecked.index) != 0;
|
||||
set isChecked(bool value) => _setFlag(SemanticsFlags.isChecked, value);
|
||||
|
||||
/// Whether the current node is selected (true) or not (false).
|
||||
bool get isSelected => (_flags & SemanticsFlags.isSelected.index) != 0;
|
||||
set isSelected(bool value) => _setFlag(SemanticsFlags.isSelected, value);
|
||||
|
||||
/// A textual description of this node.
|
||||
String get label => _label;
|
||||
String _label = '';
|
||||
@ -595,6 +599,8 @@ class SemanticsNode extends AbstractNode {
|
||||
else
|
||||
buffer.write('; unchecked');
|
||||
}
|
||||
if (isSelected)
|
||||
buffer.write('; selected');
|
||||
if (label.isNotEmpty)
|
||||
buffer.write('; "$label"');
|
||||
buffer.write(')');
|
||||
|
@ -3635,7 +3635,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
Widget child,
|
||||
this.container: false,
|
||||
this.checked,
|
||||
this.label
|
||||
this.selected,
|
||||
this.label,
|
||||
}) : assert(container != null),
|
||||
super(key: key, child: child);
|
||||
|
||||
@ -3656,6 +3657,13 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
/// state is.
|
||||
final bool checked;
|
||||
|
||||
/// If non-null indicates that this subtree represents something that can be
|
||||
/// in a selected or unselected state, and what its current state is.
|
||||
///
|
||||
/// The active tab in a tab bar for example is considered "selected", whereas
|
||||
/// all other tabs are unselected.
|
||||
final bool selected;
|
||||
|
||||
/// Provides a textual description of the widget.
|
||||
final String label;
|
||||
|
||||
@ -3663,7 +3671,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
RenderSemanticsAnnotations createRenderObject(BuildContext context) => new RenderSemanticsAnnotations(
|
||||
container: container,
|
||||
checked: checked,
|
||||
label: label
|
||||
selected: selected,
|
||||
label: label,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -3671,6 +3680,7 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
renderObject
|
||||
..container = container
|
||||
..checked = checked
|
||||
..selected = selected
|
||||
..label = label;
|
||||
}
|
||||
|
||||
@ -3680,6 +3690,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
||||
description.add('container: $container');
|
||||
if (checked != null)
|
||||
description.add('checked: $checked');
|
||||
if (selected != null)
|
||||
description.add('selected: $selected');
|
||||
if (label != null)
|
||||
description.add('label: "$label"');
|
||||
}
|
||||
|
@ -2,12 +2,15 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags, SemanticsAction;
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import '../rendering/recording_canvas.dart';
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
class StateMarker extends StatefulWidget {
|
||||
const StateMarker({ Key key, this.child }) : super(key: key);
|
||||
@ -900,6 +903,62 @@ void main() {
|
||||
rect: new Rect.fromLTRB(tabLeft + padLeft, height, tabRight - padRight, height + weight)
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('correct semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final List<Tab> tabs = new List<Tab>.generate(2, (int index) {
|
||||
return new Tab(text: 'TAB #$index');
|
||||
});
|
||||
|
||||
final TabController controller = new TabController(
|
||||
vsync: const TestVSync(),
|
||||
length: tabs.length,
|
||||
initialIndex: 0,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Material(
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
child: new TabBar(
|
||||
isScrollable: true,
|
||||
controller: controller,
|
||||
tabs: tabs,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
id: 1,
|
||||
rect: TestSemantics.fullScreen,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
actions: SemanticsAction.tap.index,
|
||||
flags: SemanticsFlags.isSelected.index,
|
||||
label: 'TAB #0',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, 46.0),
|
||||
transform: new Matrix4.translationValues(0.0, 276.0, 0.0),
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
actions: SemanticsAction.tap.index,
|
||||
label: 'TAB #1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 108.0, 46.0),
|
||||
transform: new Matrix4.translationValues(108.0, 276.0, 0.0),
|
||||
),
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('TabBar etc with zero tabs', (WidgetTester tester) async {
|
||||
final TabController controller = new TabController(
|
||||
|
Loading…
x
Reference in New Issue
Block a user