mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Assorted a11y fixes (#14395)
This commit is contained in:
parent
882be89e4c
commit
b54b576c1a
@ -19,23 +19,27 @@ class SectionCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return new DecoratedBox(
|
||||
decoration: new BoxDecoration(
|
||||
gradient: new LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: <Color>[
|
||||
section.leftColor,
|
||||
section.rightColor,
|
||||
],
|
||||
return new Semantics(
|
||||
label: section.title,
|
||||
button: true,
|
||||
child: new DecoratedBox(
|
||||
decoration: new BoxDecoration(
|
||||
gradient: new LinearGradient(
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
colors: <Color>[
|
||||
section.leftColor,
|
||||
section.rightColor,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: new Image.asset(
|
||||
section.backgroundAsset,
|
||||
package: section.backgroundAssetPackage,
|
||||
color: const Color.fromRGBO(255, 255, 255, 0.075),
|
||||
colorBlendMode: BlendMode.modulate,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: new Image.asset(
|
||||
section.backgroundAsset,
|
||||
package: section.backgroundAssetPackage,
|
||||
color: const Color.fromRGBO(255, 255, 255, 0.075),
|
||||
colorBlendMode: BlendMode.modulate,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ class ExitButton extends StatelessWidget {
|
||||
child: const Tooltip(
|
||||
message: 'Back',
|
||||
child: const Text('Exit'),
|
||||
excludeFromSemantics: true,
|
||||
),
|
||||
onPressed: () {
|
||||
// The demo is on the root navigator.
|
||||
|
@ -218,14 +218,20 @@ class _ButtonsDemoState extends State<ButtonsDemo> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
new IconButton(
|
||||
icon: const Icon(Icons.thumb_up),
|
||||
icon: const Icon(
|
||||
Icons.thumb_up,
|
||||
semanticLabel: 'Thumbs up',
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() => iconButtonToggle = !iconButtonToggle);
|
||||
},
|
||||
color: iconButtonToggle ? Theme.of(context).primaryColor : null,
|
||||
),
|
||||
const IconButton(
|
||||
icon: const Icon(Icons.thumb_up),
|
||||
icon: const Icon(
|
||||
Icons.thumb_up,
|
||||
semanticLabel: 'Thumbs up',
|
||||
),
|
||||
onPressed: null,
|
||||
)
|
||||
]
|
||||
|
@ -108,21 +108,24 @@ class _IconsDemoCard extends StatelessWidget {
|
||||
return new Card(
|
||||
child: new DefaultTextStyle(
|
||||
style: textStyle,
|
||||
child: new Table(
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
children: <TableRow> [
|
||||
new TableRow(
|
||||
children: <Widget> [
|
||||
_centeredText('Size'),
|
||||
_centeredText('Enabled'),
|
||||
_centeredText('Disabled'),
|
||||
]
|
||||
),
|
||||
_buildIconRow(18.0),
|
||||
_buildIconRow(24.0),
|
||||
_buildIconRow(36.0),
|
||||
_buildIconRow(48.0),
|
||||
],
|
||||
child: new Semantics(
|
||||
explicitChildNodes: true,
|
||||
child: new Table(
|
||||
defaultVerticalAlignment: TableCellVerticalAlignment.middle,
|
||||
children: <TableRow> [
|
||||
new TableRow(
|
||||
children: <Widget> [
|
||||
_centeredText('Size'),
|
||||
_centeredText('Enabled'),
|
||||
_centeredText('Disabled'),
|
||||
]
|
||||
),
|
||||
_buildIconRow(18.0),
|
||||
_buildIconRow(24.0),
|
||||
_buildIconRow(36.0),
|
||||
_buildIconRow(48.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -76,7 +76,10 @@ class _PersistentBottomSheetDemoState extends State<PersistentBottomSheetDemo> {
|
||||
floatingActionButton: new FloatingActionButton(
|
||||
onPressed: _showMessage,
|
||||
backgroundColor: Colors.redAccent,
|
||||
child: const Icon(Icons.add)
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
semanticLabel: 'Add',
|
||||
),
|
||||
),
|
||||
body: new Center(
|
||||
child: new RaisedButton(
|
||||
|
@ -154,7 +154,10 @@ class FullScreenCodeDialogState extends State<FullScreenCodeDialog> {
|
||||
return new Scaffold(
|
||||
appBar: new AppBar(
|
||||
leading: new IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
icon: const Icon(
|
||||
Icons.clear,
|
||||
semanticLabel: 'Close',
|
||||
),
|
||||
onPressed: () { Navigator.pop(context); }
|
||||
),
|
||||
title: const Text('Example code')
|
||||
|
@ -52,51 +52,54 @@ class _GalleryDrawerHeaderState extends State<GalleryDrawerHeader> {
|
||||
Widget build(BuildContext context) {
|
||||
final double systemTopPadding = MediaQuery.of(context).padding.top;
|
||||
|
||||
return new DrawerHeader(
|
||||
decoration: new FlutterLogoDecoration(
|
||||
margin: new EdgeInsets.fromLTRB(12.0, 12.0 + systemTopPadding, 12.0, 12.0),
|
||||
style: _logoHasName ? _logoHorizontal ? FlutterLogoStyle.horizontal
|
||||
: FlutterLogoStyle.stacked
|
||||
: FlutterLogoStyle.markOnly,
|
||||
lightColor: _logoColor.shade400,
|
||||
darkColor: _logoColor.shade900,
|
||||
textColor: widget.light ? const Color(0xFF616161) : const Color(0xFF9E9E9E),
|
||||
return new Semantics(
|
||||
label: 'Flutter',
|
||||
child: new DrawerHeader(
|
||||
decoration: new FlutterLogoDecoration(
|
||||
margin: new EdgeInsets.fromLTRB(12.0, 12.0 + systemTopPadding, 12.0, 12.0),
|
||||
style: _logoHasName ? _logoHorizontal ? FlutterLogoStyle.horizontal
|
||||
: FlutterLogoStyle.stacked
|
||||
: FlutterLogoStyle.markOnly,
|
||||
lightColor: _logoColor.shade400,
|
||||
darkColor: _logoColor.shade900,
|
||||
textColor: widget.light ? const Color(0xFF616161) : const Color(0xFF9E9E9E),
|
||||
),
|
||||
duration: const Duration(milliseconds: 750),
|
||||
child: new GestureDetector(
|
||||
onLongPress: () {
|
||||
setState(() {
|
||||
_logoHorizontal = !_logoHorizontal;
|
||||
if (!_logoHasName)
|
||||
_logoHasName = true;
|
||||
});
|
||||
},
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_logoHasName = !_logoHasName;
|
||||
});
|
||||
},
|
||||
onDoubleTap: () {
|
||||
setState(() {
|
||||
final List<MaterialColor> options = <MaterialColor>[];
|
||||
if (_logoColor != Colors.blue)
|
||||
options.addAll(<MaterialColor>[Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue]);
|
||||
if (_logoColor != Colors.amber)
|
||||
options.addAll(<MaterialColor>[Colors.amber, Colors.amber, Colors.amber]);
|
||||
if (_logoColor != Colors.red)
|
||||
options.addAll(<MaterialColor>[Colors.red, Colors.red, Colors.red]);
|
||||
if (_logoColor != Colors.indigo)
|
||||
options.addAll(<MaterialColor>[Colors.indigo, Colors.indigo, Colors.indigo]);
|
||||
if (_logoColor != Colors.pink)
|
||||
options.addAll(<MaterialColor>[Colors.pink]);
|
||||
if (_logoColor != Colors.purple)
|
||||
options.addAll(<MaterialColor>[Colors.purple]);
|
||||
if (_logoColor != Colors.cyan)
|
||||
options.addAll(<MaterialColor>[Colors.cyan]);
|
||||
_logoColor = options[new math.Random().nextInt(options.length)];
|
||||
});
|
||||
}
|
||||
),
|
||||
),
|
||||
duration: const Duration(milliseconds: 750),
|
||||
child: new GestureDetector(
|
||||
onLongPress: () {
|
||||
setState(() {
|
||||
_logoHorizontal = !_logoHorizontal;
|
||||
if (!_logoHasName)
|
||||
_logoHasName = true;
|
||||
});
|
||||
},
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_logoHasName = !_logoHasName;
|
||||
});
|
||||
},
|
||||
onDoubleTap: () {
|
||||
setState(() {
|
||||
final List<MaterialColor> options = <MaterialColor>[];
|
||||
if (_logoColor != Colors.blue)
|
||||
options.addAll(<MaterialColor>[Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue, Colors.blue]);
|
||||
if (_logoColor != Colors.amber)
|
||||
options.addAll(<MaterialColor>[Colors.amber, Colors.amber, Colors.amber]);
|
||||
if (_logoColor != Colors.red)
|
||||
options.addAll(<MaterialColor>[Colors.red, Colors.red, Colors.red]);
|
||||
if (_logoColor != Colors.indigo)
|
||||
options.addAll(<MaterialColor>[Colors.indigo, Colors.indigo, Colors.indigo]);
|
||||
if (_logoColor != Colors.pink)
|
||||
options.addAll(<MaterialColor>[Colors.pink]);
|
||||
if (_logoColor != Colors.purple)
|
||||
options.addAll(<MaterialColor>[Colors.purple]);
|
||||
if (_logoColor != Colors.cyan)
|
||||
options.addAll(<MaterialColor>[Colors.cyan]);
|
||||
_logoColor = options[new math.Random().nextInt(options.length)];
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -165,10 +165,14 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
|
||||
child: new Container(
|
||||
width: widget.mini ? _kSizeMini : _kSize,
|
||||
height: widget.mini ? _kSizeMini : _kSize,
|
||||
child: new InkWell(
|
||||
onTap: widget.onPressed,
|
||||
onHighlightChanged: _handleHighlightChanged,
|
||||
child: result,
|
||||
child: new Semantics(
|
||||
button: true,
|
||||
enabled: widget.onPressed != null,
|
||||
child: new InkWell(
|
||||
onTap: widget.onPressed,
|
||||
onHighlightChanged: _handleHighlightChanged,
|
||||
child: result,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -193,6 +193,7 @@ class IconButton extends StatelessWidget {
|
||||
|
||||
Widget result = new Semantics(
|
||||
button: true,
|
||||
enabled: onPressed != null,
|
||||
child: new ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: _kMinButtonSize, minHeight: _kMinButtonSize),
|
||||
child: new Padding(
|
||||
|
@ -48,12 +48,14 @@ class Tooltip extends StatefulWidget {
|
||||
this.padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
this.verticalOffset: 24.0,
|
||||
this.preferBelow: true,
|
||||
this.excludeFromSemantics: false,
|
||||
this.child,
|
||||
}) : assert(message != null),
|
||||
assert(height != null),
|
||||
assert(padding != null),
|
||||
assert(verticalOffset != null),
|
||||
assert(preferBelow != null),
|
||||
assert(excludeFromSemantics != null),
|
||||
super(key: key);
|
||||
|
||||
/// The text to display in the tooltip.
|
||||
@ -77,6 +79,10 @@ class Tooltip extends StatefulWidget {
|
||||
/// direction.
|
||||
final bool preferBelow;
|
||||
|
||||
/// Whether the tooltip's [message] should be excluded from the semantics
|
||||
/// tree.
|
||||
final bool excludeFromSemantics;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// {@macro flutter.widgets.child}
|
||||
@ -191,7 +197,7 @@ class _TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin {
|
||||
onLongPress: _handleLongPress,
|
||||
excludeFromSemantics: true,
|
||||
child: new Semantics(
|
||||
label: widget.message,
|
||||
label: widget.excludeFromSemantics ? null : widget.message,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
|
@ -573,13 +573,21 @@ void _tests() {
|
||||
],
|
||||
),
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isButton],
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: r'Previous month December 2015',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isButton],
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: r'Next month February 2016',
|
||||
textDirection: TextDirection.ltr,
|
||||
|
@ -2,9 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Floating Action Button control test', (WidgetTester tester) async {
|
||||
bool didPressButton = false;
|
||||
@ -132,4 +137,68 @@ void main() {
|
||||
await tester.pump();
|
||||
expect(tester.takeException().toString(), contains('xyzzy'));
|
||||
});
|
||||
|
||||
testWidgets('Floating Action Button semantics (enabled)', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Center(
|
||||
child: new FloatingActionButton(
|
||||
onPressed: () { },
|
||||
child: const Icon(Icons.add, semanticLabel: 'Add'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'Add',
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Floating Action Button semantics (disabled)', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
const Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: const Center(
|
||||
child: const FloatingActionButton(
|
||||
onPressed: null,
|
||||
child: const Icon(Icons.add, semanticLabel: 'Add'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'Add',
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
@ -275,7 +275,7 @@ void main() {
|
||||
await gesture.up();
|
||||
});
|
||||
|
||||
testWidgets('IconButton Semantics', (WidgetTester tester) async {
|
||||
testWidgets('IconButton Semantics (enabled)', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -291,8 +291,14 @@ void main() {
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 48.0, 48.0),
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
flags: <SemanticsFlag>[SemanticsFlag.isButton],
|
||||
actions: <SemanticsAction>[
|
||||
SemanticsAction.tap
|
||||
],
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
SemanticsFlag.isButton
|
||||
],
|
||||
label: 'link',
|
||||
)
|
||||
]
|
||||
@ -300,6 +306,34 @@ void main() {
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('IconButton Semantics (disabled)', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
wrap(
|
||||
child: const IconButton(
|
||||
onPressed: null,
|
||||
icon: const Icon(Icons.link, semanticLabel: 'link'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 48.0, 48.0),
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isButton
|
||||
],
|
||||
label: 'link',
|
||||
)
|
||||
]
|
||||
), ignoreId: true, ignoreTransform: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
Widget wrap({ Widget child }) {
|
||||
|
@ -602,4 +602,57 @@ void main() {
|
||||
feedback.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Semantics included', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: const Center(
|
||||
child: const Tooltip(
|
||||
message: 'Foo',
|
||||
child: const Text('Bar'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'Foo\nBar',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
), ignoreRect: true, ignoreId: true, ignoreTransform: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Semantics excluded', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: const Center(
|
||||
child: const Tooltip(
|
||||
message: 'Foo',
|
||||
child: const Text('Bar'),
|
||||
excludeFromSemantics: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'Bar',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
), ignoreRect: true, ignoreId: true, ignoreTransform: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user