Fix dropdown button semantics (#19932)

This commit is contained in:
Jonah Williams 2018-07-30 15:36:26 -07:00 committed by GitHub
parent a7595e535c
commit ee396272d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 128 additions and 19 deletions

View file

@ -139,6 +139,7 @@ class _DropdownMenuState<T> extends State<_DropdownMenu<T>> {
//
// When the menu is dismissed we just fade the entire thing out
// in the first 0.25s.
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final _DropdownRoute<T> route = widget.route;
final double unit = 0.5 / (route.items.length + 1.5);
final List<Widget> children = <Widget>[];
@ -175,24 +176,30 @@ class _DropdownMenuState<T> extends State<_DropdownMenu<T>> {
selectedIndex: route.selectedIndex,
resize: _resize,
),
child: new Material(
type: MaterialType.transparency,
textStyle: route.style,
child: new ScrollConfiguration(
behavior: const _DropdownScrollBehavior(),
child: new Scrollbar(
child: new ListView(
controller: widget.route.scrollController,
padding: kMaterialListPadding,
itemExtent: _kMenuItemHeight,
shrinkWrap: true,
children: children,
child: new Semantics(
scopesRoute: true,
namesRoute: true,
explicitChildNodes: true,
label: localizations.popupMenuLabel,
child: new Material(
type: MaterialType.transparency,
textStyle: route.style,
child: new ScrollConfiguration(
behavior: const _DropdownScrollBehavior(),
child: new Scrollbar(
child: new ListView(
controller: widget.route.scrollController,
padding: kMaterialListPadding,
itemExtent: _kMenuItemHeight,
shrinkWrap: true,
children: children,
),
),
),
),
),
),
),
);
);
}
}
@ -627,6 +634,7 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
style: _textStyle.copyWith(color: Theme.of(context).hintColor),
child: new IgnorePointer(
child: widget.hint,
ignoringSemantics: false,
),
));
}
@ -681,10 +689,13 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
);
}
return new GestureDetector(
onTap: _handleTap,
behavior: HitTestBehavior.opaque,
child: result
return new Semantics(
button: true,
child: new GestureDetector(
onTap: _handleTap,
behavior: HitTestBehavior.opaque,
child: result
),
);
}
}

View file

@ -365,7 +365,7 @@ class SemanticsData extends Diagnosticable {
scrollExtentMax,
scrollExtentMin,
transform,
customSemanticsActionIds,
ui.hashList(customSemanticsActionIds),
);
}

View file

@ -580,4 +580,102 @@ void main() {
semantics.dispose();
});
testWidgets('Dropdown button includes semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
const Key key = const Key('test');
await tester.pumpWidget(buildFrame(
buttonKey: key,
value: null,
items: menuItems,
onChanged: (String _) {},
hint: const Text('test'),
));
// By default the hint contributes the label.
expect(tester.getSemanticsData(find.byKey(key)), matchesSemanticsData(
isButton: true,
label: 'test',
hasTapAction: true,
));
await tester.pumpWidget(buildFrame(
buttonKey: key,
value: 'three',
items: menuItems,
onChanged: null,
hint: const Text('test'),
));
// Displays label of select item and is no longer tappable.
expect(tester.getSemanticsData(find.byKey(key)), matchesSemanticsData(
isButton: true,
label: 'three',
hasTapAction: true,
));
handle.dispose();
});
testWidgets('Dropdown menu includes semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
const Key key = const Key('test');
await tester.pumpWidget(buildFrame(
buttonKey: key,
value: null,
items: menuItems,
));
await tester.tap(find.byKey(key));
await tester.pumpAndSettle();
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.scopesRoute,
SemanticsFlag.namesRoute,
],
label: 'Popup menu',
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
children: <TestSemantics>[
new TestSemantics(
label: 'one',
textDirection: TextDirection.ltr,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
actions: <SemanticsAction>[SemanticsAction.tap],
),
new TestSemantics(
label: 'two',
textDirection: TextDirection.ltr,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
actions: <SemanticsAction>[SemanticsAction.tap],
),
new TestSemantics(
label: 'three',
textDirection: TextDirection.ltr,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
actions: <SemanticsAction>[SemanticsAction.tap],
),
new TestSemantics(
label: 'four',
textDirection: TextDirection.ltr,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
actions: <SemanticsAction>[SemanticsAction.tap],
),
],
),
],
),
],
),
],
),
],
), ignoreId: true, ignoreRect: true, ignoreTransform: true));
semantics.dispose();
});
}