Identify CheckBox and RadioButton as such (#14087)

* Identify CHeckBox and RadioButton as such

* review feedback
This commit is contained in:
Michael Goderbauer 2018-01-17 10:23:36 -08:00 committed by GitHub
parent 18c60d3301
commit 15af86459d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 244 additions and 18 deletions

View file

@ -1 +1 @@
05fe72d068e19c7886e8d27f9b004201d5ad1300
e7e94c6307bcefdbd4835a6f1a594a70df5dfe9a

View file

@ -118,15 +118,12 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
final ThemeData themeData = Theme.of(context);
return new Semantics(
checked: widget.value == widget.groupValue,
child: new _RadioRenderObjectWidget(
selected: widget.value == widget.groupValue,
activeColor: widget.activeColor ?? themeData.accentColor,
inactiveColor: _getInactiveColor(themeData),
onChanged: _enabled ? _handleChanged : null,
vsync: this,
)
return new _RadioRenderObjectWidget(
selected: widget.value == widget.groupValue,
activeColor: widget.activeColor ?? themeData.accentColor,
inactiveColor: _getInactiveColor(themeData),
onChanged: _enabled ? _handleChanged : null,
vsync: this,
);
}
}
@ -187,9 +184,6 @@ class _RenderRadio extends RenderToggleable {
vsync: vsync,
);
@override
bool get isInteractive => super.isInteractive && !value;
@override
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
@ -212,4 +206,10 @@ class _RenderRadio extends RenderToggleable {
canvas.drawCircle(center, _kInnerRadius * position.value, paint);
}
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isInMutuallyExclusiveGroup = true;
}
}

View file

@ -286,7 +286,8 @@ abstract class RenderToggleable extends RenderConstrainedBox {
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config.isSemanticBoundary = isInteractive;
config.isSemanticBoundary = true;
config.isEnabled = isInteractive;
if (isInteractive)
config.onTap = _handleTap;
config.isChecked = _value;

View file

@ -1099,6 +1099,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
properties.add(new FlagProperty('isEnabled', value: _hasFlag(SemanticsFlag.isEnabled), ifFalse: 'disabled'));
if (_hasFlag(SemanticsFlag.hasCheckedState))
properties.add(new FlagProperty('isChecked', value: _hasFlag(SemanticsFlag.isChecked), ifTrue: 'checked', ifFalse: 'unchecked'));
properties.add(new FlagProperty('isInMutuallyExcusiveGroup', value: _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup), ifTrue: 'mutually-exclusive'));
properties.add(new FlagProperty('isSelected', value: _hasFlag(SemanticsFlag.isSelected), ifTrue: 'selected'));
properties.add(new FlagProperty('isFocused', value: _hasFlag(SemanticsFlag.isFocused), ifTrue: 'focused'));
properties.add(new FlagProperty('isButton', value: _hasFlag(SemanticsFlag.isButton), ifTrue: 'button'));
@ -1790,6 +1791,16 @@ class SemanticsConfiguration {
_setFlag(SemanticsFlag.isChecked, value);
}
/// Whether the owning RenderObject corresponds to UI that allows the user to
/// pick one of several mutually exclusive options.
///
/// For example, a [Radio] button is in a mutually exclusive group because
/// only one radio button in that group can be marked as [isChecked].
bool get isInMutuallyExclusiveGroup => _hasFlag(SemanticsFlag.isInMutuallyExclusiveGroup);
set isInMutuallyExclusiveGroup(bool value) {
_setFlag(SemanticsFlag.isInMutuallyExclusiveGroup, value);
}
/// Whether the owning [RenderObject] currently holds the user's focus.
bool get isFocused => _hasFlag(SemanticsFlag.isFocused);
set isFocused(bool value) {

View file

@ -115,7 +115,7 @@ class BannerPainter extends CustomPainter {
void _prepare() {
_paintShadow = new Paint()
..color = const Color(0x7F000000)
..maskFilter = new MaskFilter.blur(BlurStyle.normal, 4.0);
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4.0);
_paintBanner = new Paint()
..color = color;
_textPainter = new TextPainter(

View file

@ -0,0 +1,105 @@
// Copyright 2017 The Chromium Authors. All rights reserved.
// 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/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('CheckBox semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new Material(
child: new Checkbox(
value: false,
onChanged: (bool b) { },
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(new Material(
child: new Checkbox(
value: true,
onChanged: (bool b) { },
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.hasCheckedState,
SemanticsFlag.isChecked,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material(
child: const Checkbox(
value: false,
onChanged: null,
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState,
],
),
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material(
child: const Checkbox(
value: true,
onChanged: null,
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.hasCheckedState,
SemanticsFlag.isChecked,
SemanticsFlag.hasEnabledState,
],
),
],
), ignoreRect: true, ignoreTransform: true));
semantics.dispose();
});
}

View file

@ -99,6 +99,7 @@ void main() {
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: null,
flags: <SemanticsFlag>[
@ -111,6 +112,7 @@ void main() {
label: 'aaa\nAAA',
),
new TestSemantics.rootChild(
id: 3,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 56.0, 0.0),
flags: <SemanticsFlag>[
@ -123,18 +125,20 @@ void main() {
label: 'bbb\nBBB',
),
new TestSemantics.rootChild(
id: 5,
rect: new Rect.fromLTWH(0.0, 0.0, 800.0, 56.0),
transform: new Matrix4.translationValues(0.0, 112.0, 0.0),
flags: <SemanticsFlag>[
SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled
SemanticsFlag.isEnabled,
SemanticsFlag.isInMutuallyExclusiveGroup,
],
actions: SemanticsAction.tap.index,
label: 'CCC\nccc',
),
],
), ignoreId: true));
)));
});
}

View file

@ -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/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import '../widgets/semantics_tester.dart';
void main() {
testWidgets('Radio control test', (WidgetTester tester) async {
final Key key = new UniqueKey();
@ -57,4 +62,104 @@ void main() {
expect(log, isEmpty);
});
testWidgets('Radio semantics', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(new Material(
child: new Radio<int>(
value: 1,
groupValue: 2,
onChanged: (int i) { },
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(new Material(
child: new Radio<int>(
value: 2,
groupValue: 2,
onChanged: (int i) { },
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
SemanticsFlag.hasCheckedState,
SemanticsFlag.isChecked,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
actions: <SemanticsAction>[
SemanticsAction.tap,
],
),
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material(
child: const Radio<int>(
value: 1,
groupValue: 2,
onChanged: null,
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
SemanticsFlag.hasCheckedState,
SemanticsFlag.hasEnabledState,
],
),
],
), ignoreRect: true, ignoreTransform: true));
await tester.pumpWidget(const Material(
child: const Radio<int>(
value: 2,
groupValue: 2,
onChanged: null,
),
));
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
flags: <SemanticsFlag>[
SemanticsFlag.isInMutuallyExclusiveGroup,
SemanticsFlag.hasCheckedState,
SemanticsFlag.isChecked,
SemanticsFlag.hasEnabledState,
],
),
],
), ignoreRect: true, ignoreTransform: true));
semantics.dispose();
});
}

View file

@ -206,7 +206,7 @@ void main() {
expect(
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n'
'SemanticsNode#1(owner: null, isMergedIntoParent: false, mergeAllDescendantsIntoThisNode: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isInMutuallyExcusiveGroup: false, isSelected: false, isFocused: false, isButton: false, isTextField: false, label: "", value: "", increasedValue: "", decreasedValue: "", hint: "", textDirection: null)\n'
);
final SemanticsConfiguration config = new SemanticsConfiguration()