mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add flag to ThemeData to expand tap targets of certain material widgets (#18369)
This commit is contained in:
parent
6172e23c21
commit
989f5741da
@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'button_theme.dart';
|
||||
@ -11,6 +12,7 @@ import 'constants.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'material.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
/// Creates a button based on [Semantics], [Material], and [InkWell]
|
||||
/// widgets.
|
||||
@ -38,13 +40,14 @@ class RawMaterialButton extends StatefulWidget {
|
||||
this.elevation = 2.0,
|
||||
this.highlightElevation = 8.0,
|
||||
this.disabledElevation = 0.0,
|
||||
this.outerPadding,
|
||||
this.padding = EdgeInsets.zero,
|
||||
this.constraints = const BoxConstraints(minWidth: 88.0, minHeight: 36.0),
|
||||
this.shape = const RoundedRectangleBorder(),
|
||||
this.animationDuration = kThemeChangeDuration,
|
||||
MaterialTapTargetSize materialTapTargetSize,
|
||||
this.child,
|
||||
}) : assert(shape != null),
|
||||
}) : this.materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded,
|
||||
assert(shape != null),
|
||||
assert(elevation != null),
|
||||
assert(highlightElevation != null),
|
||||
assert(disabledElevation != null),
|
||||
@ -58,10 +61,6 @@ class RawMaterialButton extends StatefulWidget {
|
||||
/// If this is set to null, the button will be disabled, see [enabled].
|
||||
final VoidCallback onPressed;
|
||||
|
||||
/// Padding to increase the size of the gesture detector which doesn't
|
||||
/// increase the visible material of the button.
|
||||
final EdgeInsets outerPadding;
|
||||
|
||||
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
|
||||
/// callback.
|
||||
final ValueChanged<bool> onHighlightChanged;
|
||||
@ -138,6 +137,15 @@ class RawMaterialButton extends StatefulWidget {
|
||||
/// property to a non-null value.
|
||||
bool get enabled => onPressed != null;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [MaterialTapTargetSize.padded].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
_RawMaterialButtonState createState() => new _RawMaterialButtonState();
|
||||
}
|
||||
@ -186,18 +194,23 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (widget.outerPadding != null) {
|
||||
result = new GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
excludeFromSemantics: true,
|
||||
onTap: widget.onPressed,
|
||||
child: new Padding(
|
||||
padding: widget.outerPadding,
|
||||
child: result
|
||||
),
|
||||
);
|
||||
BoxConstraints constraints;
|
||||
switch (widget.materialTapTargetSize) {
|
||||
case MaterialTapTargetSize.padded:
|
||||
constraints = const BoxConstraints(minWidth: 48.0, minHeight: 48.0);
|
||||
break;
|
||||
case MaterialTapTargetSize.shrinkWrap:
|
||||
constraints = const BoxConstraints();
|
||||
break;
|
||||
}
|
||||
result = new _ButtonRedirectingHitDetectionWidget(
|
||||
constraints: constraints,
|
||||
child: new Center(
|
||||
child: result,
|
||||
widthFactor: 1.0,
|
||||
heightFactor: 1.0,
|
||||
),
|
||||
);
|
||||
|
||||
return new Semantics(
|
||||
container: true,
|
||||
@ -248,6 +261,7 @@ class MaterialButton extends StatelessWidget {
|
||||
this.minWidth,
|
||||
this.height,
|
||||
this.padding,
|
||||
this.materialTapTargetSize,
|
||||
@required this.onPressed,
|
||||
this.child
|
||||
}) : super(key: key);
|
||||
@ -353,6 +367,15 @@ class MaterialButton extends StatelessWidget {
|
||||
/// {@macro flutter.widgets.child}
|
||||
final Widget child;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
/// Whether the button is enabled or disabled. Buttons are disabled by default. To
|
||||
/// enable a button, set its [onPressed] property to a non-null value.
|
||||
bool get enabled => onPressed != null;
|
||||
@ -412,6 +435,7 @@ class MaterialButton extends StatelessWidget {
|
||||
),
|
||||
shape: buttonTheme.shape,
|
||||
child: child,
|
||||
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
|
||||
);
|
||||
}
|
||||
|
||||
@ -421,3 +445,38 @@ class MaterialButton extends StatelessWidget {
|
||||
properties.add(new FlagProperty('enabled', value: enabled, ifFalse: 'disabled'));
|
||||
}
|
||||
}
|
||||
|
||||
/// Redirects the position passed to [RenderBox.hitTest] to the center of the widget.
|
||||
///
|
||||
/// The primary purpose of this widget is to allow padding around [Material] widgets
|
||||
/// to trigger the child ink feature without increasing the size of the material.
|
||||
class _ButtonRedirectingHitDetectionWidget extends SingleChildRenderObjectWidget {
|
||||
const _ButtonRedirectingHitDetectionWidget({
|
||||
Key key,
|
||||
Widget child,
|
||||
this.constraints
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final BoxConstraints constraints;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return new _RenderButtonRedirectingHitDetection(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, covariant _RenderButtonRedirectingHitDetection renderObject) {
|
||||
renderObject.additionalConstraints = constraints;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderButtonRedirectingHitDetection extends RenderConstrainedBox {
|
||||
_RenderButtonRedirectingHitDetection (BoxConstraints additionalConstraints) : super(additionalConstraints: additionalConstraints);
|
||||
|
||||
@override
|
||||
bool hitTest(HitTestResult result, {Offset position}) {
|
||||
if (!size.contains(position))
|
||||
return false;
|
||||
return child.hitTest(result, position: size.center(Offset.zero));
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'toggleable.dart';
|
||||
|
||||
/// A material design checkbox.
|
||||
@ -58,6 +59,7 @@ class Checkbox extends StatefulWidget {
|
||||
this.tristate = false,
|
||||
@required this.onChanged,
|
||||
this.activeColor,
|
||||
this.materialTapTargetSize,
|
||||
}) : assert(tristate != null),
|
||||
assert(tristate || value != null),
|
||||
super(key: key);
|
||||
@ -113,6 +115,15 @@ class Checkbox extends StatefulWidget {
|
||||
/// If tristate is false (the default), [value] must not be null.
|
||||
final bool tristate;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
/// The width of a checkbox widget.
|
||||
static const double width = 18.0;
|
||||
|
||||
@ -125,12 +136,23 @@ class _CheckboxState extends State<Checkbox> with TickerProviderStateMixin {
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
Size size;
|
||||
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
|
||||
case MaterialTapTargetSize.padded:
|
||||
size = const Size(2 * kRadialReactionRadius + 8.0, 2 * kRadialReactionRadius + 8.0);
|
||||
break;
|
||||
case MaterialTapTargetSize.shrinkWrap:
|
||||
size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
|
||||
break;
|
||||
}
|
||||
final BoxConstraints additionalConstraints = new BoxConstraints.tight(size);
|
||||
return new _CheckboxRenderObjectWidget(
|
||||
value: widget.value,
|
||||
tristate: widget.tristate,
|
||||
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
|
||||
inactiveColor: widget.onChanged != null ? themeData.unselectedWidgetColor : themeData.disabledColor,
|
||||
onChanged: widget.onChanged,
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
@ -145,6 +167,7 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
@required this.inactiveColor,
|
||||
@required this.onChanged,
|
||||
@required this.vsync,
|
||||
@required this.additionalConstraints,
|
||||
}) : assert(tristate != null),
|
||||
assert(tristate || value != null),
|
||||
assert(activeColor != null),
|
||||
@ -158,6 +181,7 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
final Color inactiveColor;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final TickerProvider vsync;
|
||||
final BoxConstraints additionalConstraints;
|
||||
|
||||
@override
|
||||
_RenderCheckbox createRenderObject(BuildContext context) => new _RenderCheckbox(
|
||||
@ -167,6 +191,7 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
inactiveColor: inactiveColor,
|
||||
onChanged: onChanged,
|
||||
vsync: vsync,
|
||||
additionalConstraints: additionalConstraints,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -177,6 +202,7 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..activeColor = activeColor
|
||||
..inactiveColor = inactiveColor
|
||||
..onChanged = onChanged
|
||||
..additionalConstraints = additionalConstraints
|
||||
..vsync = vsync;
|
||||
}
|
||||
}
|
||||
@ -191,6 +217,7 @@ class _RenderCheckbox extends RenderToggleable {
|
||||
bool tristate,
|
||||
Color activeColor,
|
||||
Color inactiveColor,
|
||||
BoxConstraints additionalConstraints,
|
||||
ValueChanged<bool> onChanged,
|
||||
@required TickerProvider vsync,
|
||||
}): _oldValue = value,
|
||||
@ -200,7 +227,7 @@ class _RenderCheckbox extends RenderToggleable {
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
onChanged: onChanged,
|
||||
size: const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius),
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: vsync,
|
||||
);
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'checkbox.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
/// A [ListTile] with a [Checkbox]. In other words, a checkbox with a label.
|
||||
///
|
||||
@ -173,6 +174,7 @@ class CheckboxListTile extends StatelessWidget {
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: activeColor,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
);
|
||||
Widget leading, trailing;
|
||||
switch (controlAffinity) {
|
||||
|
||||
@ -17,6 +17,7 @@ import 'ink_well.dart';
|
||||
import 'material.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'tooltip.dart';
|
||||
|
||||
// Some design constants
|
||||
@ -99,6 +100,15 @@ abstract class ChipAttributes {
|
||||
/// By default, this is 4 logical pixels at the beginning and the end of the
|
||||
/// label, and zero on top and bottom.
|
||||
EdgeInsetsGeometry get labelPadding;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
MaterialTapTargetSize get materialTapTargetSize;
|
||||
}
|
||||
|
||||
/// An interface for material design chips that can be deleted.
|
||||
@ -423,6 +433,7 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
|
||||
this.shape,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
this.materialTapTargetSize,
|
||||
}) : assert(label != null),
|
||||
super(key: key);
|
||||
|
||||
@ -448,6 +459,8 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
|
||||
final Color deleteIconColor;
|
||||
@override
|
||||
final String deleteButtonTooltipMessage;
|
||||
@override
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -465,6 +478,7 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri
|
||||
shape: shape,
|
||||
backgroundColor: backgroundColor,
|
||||
padding: padding,
|
||||
materialTapTargetSize: materialTapTargetSize,
|
||||
isEnabled: true,
|
||||
);
|
||||
}
|
||||
@ -547,6 +561,7 @@ class InputChip extends StatelessWidget
|
||||
this.shape,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
this.materialTapTargetSize,
|
||||
}) : assert(selected != null),
|
||||
assert(isEnabled != null),
|
||||
assert(label != null),
|
||||
@ -588,6 +603,8 @@ class InputChip extends StatelessWidget
|
||||
final Color backgroundColor;
|
||||
@override
|
||||
final EdgeInsetsGeometry padding;
|
||||
@override
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -611,6 +628,7 @@ class InputChip extends StatelessWidget
|
||||
shape: shape,
|
||||
backgroundColor: backgroundColor,
|
||||
padding: padding,
|
||||
materialTapTargetSize: materialTapTargetSize,
|
||||
isEnabled: isEnabled && (onSelected != null || onDeleted != null || onPressed != null),
|
||||
);
|
||||
}
|
||||
@ -689,6 +707,7 @@ class ChoiceChip extends StatelessWidget
|
||||
this.shape,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
this.materialTapTargetSize,
|
||||
}) : assert(selected != null),
|
||||
assert(label != null),
|
||||
super(key: key);
|
||||
@ -717,6 +736,8 @@ class ChoiceChip extends StatelessWidget
|
||||
final Color backgroundColor;
|
||||
@override
|
||||
final EdgeInsetsGeometry padding;
|
||||
@override
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
bool get isEnabled => onSelected != null;
|
||||
@ -741,6 +762,7 @@ class ChoiceChip extends StatelessWidget
|
||||
backgroundColor: backgroundColor,
|
||||
padding: padding,
|
||||
isEnabled: isEnabled,
|
||||
materialTapTargetSize: materialTapTargetSize,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -852,6 +874,7 @@ class FilterChip extends StatelessWidget
|
||||
this.shape,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
this.materialTapTargetSize,
|
||||
}) : assert(selected != null),
|
||||
assert(label != null),
|
||||
super(key: key);
|
||||
@ -880,6 +903,8 @@ class FilterChip extends StatelessWidget
|
||||
final Color backgroundColor;
|
||||
@override
|
||||
final EdgeInsetsGeometry padding;
|
||||
@override
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
bool get isEnabled => onSelected != null;
|
||||
@ -901,6 +926,7 @@ class FilterChip extends StatelessWidget
|
||||
selectedColor: selectedColor,
|
||||
padding: padding,
|
||||
isEnabled: isEnabled,
|
||||
materialTapTargetSize: materialTapTargetSize,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -966,6 +992,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
|
||||
this.shape,
|
||||
this.backgroundColor,
|
||||
this.padding,
|
||||
this.materialTapTargetSize,
|
||||
}) : assert(label != null),
|
||||
assert(
|
||||
onPressed != null,
|
||||
@ -992,6 +1019,8 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
|
||||
final Color backgroundColor;
|
||||
@override
|
||||
final EdgeInsetsGeometry padding;
|
||||
@override
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -1007,6 +1036,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip
|
||||
padding: padding,
|
||||
labelPadding: labelPadding,
|
||||
isEnabled: true,
|
||||
materialTapTargetSize: materialTapTargetSize
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1076,6 +1106,7 @@ class RawChip extends StatefulWidget
|
||||
this.tooltip,
|
||||
this.shape,
|
||||
this.backgroundColor,
|
||||
this.materialTapTargetSize,
|
||||
}) : assert(label != null),
|
||||
assert(isEnabled != null),
|
||||
deleteIcon = deleteIcon ?? _kDefaultDeleteIcon,
|
||||
@ -1117,6 +1148,8 @@ class RawChip extends StatefulWidget
|
||||
final Color backgroundColor;
|
||||
@override
|
||||
final EdgeInsetsGeometry padding;
|
||||
@override
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
/// Whether or not to show a check mark when [selected] is true.
|
||||
///
|
||||
@ -1368,7 +1401,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
|
||||
final TextDirection textDirection = Directionality.of(context);
|
||||
final ShapeBorder shape = widget.shape ?? chipTheme.shape;
|
||||
|
||||
return new Material(
|
||||
Widget result = new Material(
|
||||
elevation: isTapping ? _kPressElevation : 0.0,
|
||||
animationDuration: pressedAnimationDuration,
|
||||
shape: shape,
|
||||
@ -1428,6 +1461,68 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
|
||||
),
|
||||
),
|
||||
);
|
||||
BoxConstraints constraints;
|
||||
switch (widget.materialTapTargetSize ?? theme.materialTapTargetSize) {
|
||||
case MaterialTapTargetSize.padded:
|
||||
constraints = const BoxConstraints(minHeight: 48.0);
|
||||
break;
|
||||
case MaterialTapTargetSize.shrinkWrap:
|
||||
constraints = const BoxConstraints();
|
||||
break;
|
||||
}
|
||||
result = _ChipRedirectingHitDetectionWidget(
|
||||
constraints: constraints,
|
||||
child: new Center(
|
||||
child: result,
|
||||
widthFactor: 1.0,
|
||||
heightFactor: 1.0,
|
||||
),
|
||||
);
|
||||
return new Semantics(
|
||||
container: true,
|
||||
selected: widget.selected,
|
||||
enabled: canTap ? widget.isEnabled : null,
|
||||
child: result,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Redirects the [position.dy] passed to [RenderBox.hitTest] to the vertical
|
||||
/// center of the widget.
|
||||
///
|
||||
/// The primary purpose of this widget is to allow padding around the [RawChip]
|
||||
/// to trigger the child ink feature without increasing the size of the material.
|
||||
class _ChipRedirectingHitDetectionWidget extends SingleChildRenderObjectWidget {
|
||||
const _ChipRedirectingHitDetectionWidget({
|
||||
Key key,
|
||||
Widget child,
|
||||
this.constraints,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final BoxConstraints constraints;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return new _RenderChipRedirectingHitDetection(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, covariant _RenderChipRedirectingHitDetection renderObject) {
|
||||
renderObject.additionalConstraints = constraints;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderChipRedirectingHitDetection extends RenderConstrainedBox {
|
||||
_RenderChipRedirectingHitDetection(BoxConstraints additionalConstraints) : super(additionalConstraints: additionalConstraints);
|
||||
|
||||
@override
|
||||
bool hitTest(HitTestResult result, {Offset position}) {
|
||||
if (!size.contains(position))
|
||||
return false;
|
||||
// Only redirects hit detection which occurs above and below the render object.
|
||||
// In order to make this assumption true, I have removed the minimum width
|
||||
// constraints, since any reasonable chip would be at least that wide.
|
||||
return child.hitTest(result, position: new Offset(position.dx, size.height / 2));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1932,6 +2027,28 @@ class _RenderChip extends RenderBox {
|
||||
return new Size(deleteIconWidth, deleteIconHeight);
|
||||
}
|
||||
|
||||
@override
|
||||
bool hitTest(HitTestResult result, {Offset position}) {
|
||||
if (!size.contains(position))
|
||||
return false;
|
||||
RenderBox hitTestChild;
|
||||
switch (textDirection) {
|
||||
case TextDirection.ltr:
|
||||
if (position.dx / size.width > 0.66)
|
||||
hitTestChild = deleteIcon ?? label ?? avatar;
|
||||
else
|
||||
hitTestChild = label ?? avatar;
|
||||
break;
|
||||
case TextDirection.rtl:
|
||||
if (position.dx / size.width < 0.33)
|
||||
hitTestChild = deleteIcon ?? label ?? avatar;
|
||||
else
|
||||
hitTestChild = label ?? avatar;
|
||||
break;
|
||||
}
|
||||
return hitTestChild?.hitTest(result, position: hitTestChild.size.center(Offset.zero)) ?? false;
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
final BoxConstraints contentConstraints = constraints.loosen();
|
||||
@ -2250,20 +2367,4 @@ class _RenderChip extends RenderBox {
|
||||
|
||||
@override
|
||||
bool hitTestSelf(Offset position) => deleteButtonRect.contains(position) || pressRect.contains(position);
|
||||
|
||||
@override
|
||||
bool hitTestChildren(HitTestResult result, {@required Offset position}) {
|
||||
assert(position != null);
|
||||
if (deleteIcon != null && deleteButtonRect.contains(position)) {
|
||||
// This simulates a position at the center of the delete icon if the hit
|
||||
// on the chip is inside of the delete area.
|
||||
return deleteIcon.hitTest(result, position: (Offset.zero & _boxSize(deleteIcon)).center);
|
||||
}
|
||||
for (RenderBox child in _children) {
|
||||
if (child.hasSize && child.hitTest(result, position: position - _boxParentData(child).offset)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ import 'button.dart';
|
||||
import 'button_theme.dart';
|
||||
import 'colors.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
/// A material design "flat button".
|
||||
///
|
||||
@ -62,6 +63,7 @@ class FlatButton extends StatelessWidget {
|
||||
this.colorBrightness,
|
||||
this.padding,
|
||||
this.shape,
|
||||
this.materialTapTargetSize,
|
||||
@required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -85,6 +87,7 @@ class FlatButton extends StatelessWidget {
|
||||
this.splashColor,
|
||||
this.colorBrightness,
|
||||
this.shape,
|
||||
this.materialTapTargetSize,
|
||||
@required Widget icon,
|
||||
@required Widget label,
|
||||
}) : assert(icon != null),
|
||||
@ -185,6 +188,15 @@ class FlatButton extends StatelessWidget {
|
||||
/// Defaults to the theme's brightness, [ThemeData.brightness].
|
||||
final Brightness colorBrightness;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
/// The widget below this widget in the tree.
|
||||
///
|
||||
/// Typically a [Text] widget in all caps.
|
||||
@ -290,6 +302,7 @@ class FlatButton extends StatelessWidget {
|
||||
splashColor: _getSplashColor(theme, buttonTheme),
|
||||
elevation: 0.0,
|
||||
highlightElevation: 0.0,
|
||||
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
|
||||
padding: padding ?? buttonTheme.padding,
|
||||
constraints: buttonTheme.constraints,
|
||||
shape: shape ?? buttonTheme.shape,
|
||||
@ -311,5 +324,6 @@ class FlatButton extends StatelessWidget {
|
||||
properties.add(new DiagnosticsProperty<Brightness>('colorBrightness', colorBrightness, defaultValue: null));
|
||||
properties.add(new DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
|
||||
properties.add(new DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
|
||||
properties.add(new DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'button.dart';
|
||||
import 'scaffold.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'tooltip.dart';
|
||||
|
||||
const BoxConstraints _kSizeConstraints = const BoxConstraints.tightFor(
|
||||
@ -67,6 +68,7 @@ class FloatingActionButton extends StatefulWidget {
|
||||
@required this.onPressed,
|
||||
this.mini = false,
|
||||
this.shape = const CircleBorder(),
|
||||
this.materialTapTargetSize,
|
||||
this.isExtended = false,
|
||||
}) : assert(elevation != null),
|
||||
assert(highlightElevation != null),
|
||||
@ -92,6 +94,7 @@ class FloatingActionButton extends StatefulWidget {
|
||||
@required this.onPressed,
|
||||
this.shape = const StadiumBorder(),
|
||||
this.isExtended = true,
|
||||
this.materialTapTargetSize,
|
||||
@required Widget icon,
|
||||
@required Widget label,
|
||||
}) : assert(elevation != null),
|
||||
@ -196,6 +199,15 @@ class FloatingActionButton extends StatefulWidget {
|
||||
/// floating action buttons are scaled and faded in.
|
||||
final bool isExtended;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
final BoxConstraints _sizeConstraints;
|
||||
|
||||
@override
|
||||
@ -241,7 +253,7 @@ class _FloatingActionButtonState extends State<FloatingActionButton> {
|
||||
onHighlightChanged: _handleHighlightChanged,
|
||||
elevation: _highlight ? widget.highlightElevation : widget.elevation,
|
||||
constraints: widget._sizeConstraints,
|
||||
outerPadding: widget.mini ? const EdgeInsets.all(4.0) : null,
|
||||
materialTapTargetSize: widget.materialTapTargetSize ?? theme.materialTapTargetSize,
|
||||
fillColor: widget.backgroundColor ?? theme.accentColor,
|
||||
textStyle: theme.accentTextTheme.button.copyWith(
|
||||
color: foregroundColor,
|
||||
|
||||
@ -31,7 +31,7 @@ abstract class InputBorder extends ShapeBorder {
|
||||
/// No input border.
|
||||
///
|
||||
/// Use this value with [InputDecoration.border] to specify that no border
|
||||
/// should be drawn. The [InputDecoration.collapsed] constructor sets
|
||||
/// should be drawn. The [InputDecoration.shrinkWrap] constructor sets
|
||||
/// its border to this value.
|
||||
static const InputBorder none = const _NoInputBorder();
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'toggleable.dart';
|
||||
|
||||
const double _kOuterRadius = 8.0;
|
||||
@ -54,7 +55,8 @@ class Radio<T> extends StatefulWidget {
|
||||
@required this.value,
|
||||
@required this.groupValue,
|
||||
@required this.onChanged,
|
||||
this.activeColor
|
||||
this.activeColor,
|
||||
this.materialTapTargetSize,
|
||||
}) : super(key: key);
|
||||
|
||||
/// The value represented by this radio button.
|
||||
@ -96,6 +98,15 @@ class Radio<T> extends StatefulWidget {
|
||||
/// Defaults to [ThemeData.toggleableActiveColor].
|
||||
final Color activeColor;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
_RadioState<T> createState() => new _RadioState<T>();
|
||||
}
|
||||
@ -116,11 +127,22 @@ class _RadioState<T> extends State<Radio<T>> with TickerProviderStateMixin {
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
Size size;
|
||||
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
|
||||
case MaterialTapTargetSize.padded:
|
||||
size = const Size(2 * kRadialReactionRadius + 8.0, 2 * kRadialReactionRadius + 8.0);
|
||||
break;
|
||||
case MaterialTapTargetSize.shrinkWrap:
|
||||
size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
|
||||
break;
|
||||
}
|
||||
final BoxConstraints additionalConstraints = new BoxConstraints.tight(size);
|
||||
return new _RadioRenderObjectWidget(
|
||||
selected: widget.value == widget.groupValue,
|
||||
activeColor: widget.activeColor ?? themeData.toggleableActiveColor,
|
||||
inactiveColor: _getInactiveColor(themeData),
|
||||
onChanged: _enabled ? _handleChanged : null,
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
@ -132,6 +154,7 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
@required this.selected,
|
||||
@required this.activeColor,
|
||||
@required this.inactiveColor,
|
||||
@required this.additionalConstraints,
|
||||
this.onChanged,
|
||||
@required this.vsync,
|
||||
}) : assert(selected != null),
|
||||
@ -145,6 +168,7 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
final Color activeColor;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final TickerProvider vsync;
|
||||
final BoxConstraints additionalConstraints;
|
||||
|
||||
@override
|
||||
_RenderRadio createRenderObject(BuildContext context) => new _RenderRadio(
|
||||
@ -153,6 +177,7 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
inactiveColor: inactiveColor,
|
||||
onChanged: onChanged,
|
||||
vsync: vsync,
|
||||
additionalConstraints: additionalConstraints,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -162,6 +187,7 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..activeColor = activeColor
|
||||
..inactiveColor = inactiveColor
|
||||
..onChanged = onChanged
|
||||
..additionalConstraints = additionalConstraints
|
||||
..vsync = vsync;
|
||||
}
|
||||
}
|
||||
@ -172,6 +198,7 @@ class _RenderRadio extends RenderToggleable {
|
||||
Color activeColor,
|
||||
Color inactiveColor,
|
||||
ValueChanged<bool> onChanged,
|
||||
BoxConstraints additionalConstraints,
|
||||
@required TickerProvider vsync,
|
||||
}): super(
|
||||
value: value,
|
||||
@ -179,7 +206,7 @@ class _RenderRadio extends RenderToggleable {
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
onChanged: onChanged,
|
||||
size: const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius),
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: vsync,
|
||||
);
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'radio.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
/// A [ListTile] with a [Radio]. In other words, a radio button with a label.
|
||||
///
|
||||
@ -198,6 +199,7 @@ class RadioListTile<T> extends StatelessWidget {
|
||||
groupValue: groupValue,
|
||||
onChanged: onChanged,
|
||||
activeColor: activeColor,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
);
|
||||
Widget leading, trailing;
|
||||
switch (controlAffinity) {
|
||||
|
||||
@ -10,6 +10,7 @@ import 'button_theme.dart';
|
||||
import 'colors.dart';
|
||||
import 'constants.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
/// A material design "raised button".
|
||||
///
|
||||
@ -62,6 +63,7 @@ class RaisedButton extends StatelessWidget {
|
||||
this.disabledElevation = 0.0,
|
||||
this.padding,
|
||||
this.shape,
|
||||
this.materialTapTargetSize,
|
||||
this.animationDuration = kThemeChangeDuration,
|
||||
this.child,
|
||||
}) : assert(elevation != null),
|
||||
@ -94,6 +96,7 @@ class RaisedButton extends StatelessWidget {
|
||||
this.highlightElevation = 8.0,
|
||||
this.disabledElevation = 0.0,
|
||||
this.shape,
|
||||
this.materialTapTargetSize,
|
||||
this.animationDuration = kThemeChangeDuration,
|
||||
@required Widget icon,
|
||||
@required Widget label,
|
||||
@ -289,6 +292,15 @@ class RaisedButton extends StatelessWidget {
|
||||
/// The default value is [kThemeChangeDuration].
|
||||
final Duration animationDuration;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
Brightness _getBrightness(ThemeData theme) {
|
||||
return colorBrightness ?? theme.brightness;
|
||||
}
|
||||
@ -380,6 +392,7 @@ class RaisedButton extends StatelessWidget {
|
||||
shape: shape ?? buttonTheme.shape,
|
||||
animationDuration: animationDuration,
|
||||
child: child,
|
||||
materialTapTargetSize: materialTapTargetSize ?? theme.materialTapTargetSize,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -12,8 +12,17 @@ import 'constants.dart';
|
||||
import 'debug.dart';
|
||||
import 'shadows.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'toggleable.dart';
|
||||
|
||||
const double _kTrackHeight = 14.0;
|
||||
const double _kTrackWidth = 33.0;
|
||||
const double _kTrackRadius = _kTrackHeight / 2.0;
|
||||
const double _kThumbRadius = 10.0;
|
||||
const double _kSwitchWidth = _kTrackWidth - 2 * _kTrackRadius + 2 * kRadialReactionRadius;
|
||||
const double _kSwitchHeight = 2 * kRadialReactionRadius + 8.0;
|
||||
const double _kSwitchHeightCollapsed = 2 * kRadialReactionRadius;
|
||||
|
||||
/// A material design switch.
|
||||
///
|
||||
/// Used to toggle the on/off state of a single setting.
|
||||
@ -54,7 +63,8 @@ class Switch extends StatefulWidget {
|
||||
this.inactiveThumbColor,
|
||||
this.inactiveTrackColor,
|
||||
this.activeThumbImage,
|
||||
this.inactiveThumbImage
|
||||
this.inactiveThumbImage,
|
||||
this.materialTapTargetSize,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Whether this switch is on or off.
|
||||
@ -112,6 +122,15 @@ class Switch extends StatefulWidget {
|
||||
/// An image to use on the thumb of this switch when the switch is off.
|
||||
final ImageProvider inactiveThumbImage;
|
||||
|
||||
/// Configures the minimum size of the tap target.
|
||||
///
|
||||
/// Defaults to [ThemeData.materialTapTargetSize].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
@override
|
||||
_SwitchState createState() => new _SwitchState();
|
||||
|
||||
@ -142,6 +161,16 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
|
||||
inactiveThumbColor = widget.inactiveThumbColor ?? (isDark ? Colors.grey.shade800 : Colors.grey.shade400);
|
||||
inactiveTrackColor = widget.inactiveTrackColor ?? (isDark ? Colors.white10 : Colors.black12);
|
||||
}
|
||||
Size size;
|
||||
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
|
||||
case MaterialTapTargetSize.padded:
|
||||
size = const Size(_kSwitchWidth, _kSwitchHeight);
|
||||
break;
|
||||
case MaterialTapTargetSize.shrinkWrap:
|
||||
size = const Size(_kSwitchWidth, _kSwitchHeightCollapsed);
|
||||
break;
|
||||
}
|
||||
final BoxConstraints additionalConstraints = new BoxConstraints.tight(size);
|
||||
|
||||
return new _SwitchRenderObjectWidget(
|
||||
value: widget.value,
|
||||
@ -153,6 +182,7 @@ class _SwitchState extends State<Switch> with TickerProviderStateMixin {
|
||||
inactiveTrackColor: inactiveTrackColor,
|
||||
configuration: createLocalImageConfiguration(context),
|
||||
onChanged: widget.onChanged,
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: this,
|
||||
);
|
||||
}
|
||||
@ -171,6 +201,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
this.configuration,
|
||||
this.onChanged,
|
||||
this.vsync,
|
||||
this.additionalConstraints,
|
||||
}) : super(key: key);
|
||||
|
||||
final bool value;
|
||||
@ -183,6 +214,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
final ImageConfiguration configuration;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final TickerProvider vsync;
|
||||
final BoxConstraints additionalConstraints;
|
||||
|
||||
@override
|
||||
_RenderSwitch createRenderObject(BuildContext context) {
|
||||
@ -197,6 +229,7 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
configuration: configuration,
|
||||
onChanged: onChanged,
|
||||
textDirection: Directionality.of(context),
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: vsync,
|
||||
);
|
||||
}
|
||||
@ -214,17 +247,11 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
|
||||
..configuration = configuration
|
||||
..onChanged = onChanged
|
||||
..textDirection = Directionality.of(context)
|
||||
..additionalConstraints = additionalConstraints
|
||||
..vsync = vsync;
|
||||
}
|
||||
}
|
||||
|
||||
const double _kTrackHeight = 14.0;
|
||||
const double _kTrackWidth = 33.0;
|
||||
const double _kTrackRadius = _kTrackHeight / 2.0;
|
||||
const double _kThumbRadius = 10.0;
|
||||
const double _kSwitchWidth = _kTrackWidth - 2 * _kTrackRadius + 2 * kRadialReactionRadius;
|
||||
const double _kSwitchHeight = 2 * kRadialReactionRadius;
|
||||
|
||||
class _RenderSwitch extends RenderToggleable {
|
||||
_RenderSwitch({
|
||||
bool value,
|
||||
@ -235,6 +262,7 @@ class _RenderSwitch extends RenderToggleable {
|
||||
Color activeTrackColor,
|
||||
Color inactiveTrackColor,
|
||||
ImageConfiguration configuration,
|
||||
BoxConstraints additionalConstraints,
|
||||
@required TextDirection textDirection,
|
||||
ValueChanged<bool> onChanged,
|
||||
@required TickerProvider vsync,
|
||||
@ -251,7 +279,7 @@ class _RenderSwitch extends RenderToggleable {
|
||||
activeColor: activeColor,
|
||||
inactiveColor: inactiveColor,
|
||||
onChanged: onChanged,
|
||||
size: const Size(_kSwitchWidth, _kSwitchHeight),
|
||||
additionalConstraints: additionalConstraints,
|
||||
vsync: vsync,
|
||||
) {
|
||||
_drag = new HorizontalDragGestureRecognizer()
|
||||
|
||||
@ -7,6 +7,7 @@ import 'package:flutter/widgets.dart';
|
||||
import 'list_tile.dart';
|
||||
import 'switch.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
/// A [ListTile] with a [Switch]. In other words, a switch with a label.
|
||||
///
|
||||
@ -171,6 +172,7 @@ class SwitchListTile extends StatelessWidget {
|
||||
activeColor: activeColor,
|
||||
activeThumbImage: activeThumbImage,
|
||||
inactiveThumbImage: inactiveThumbImage,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
);
|
||||
return new MergeSemantics(
|
||||
child: ListTileTheme.merge(
|
||||
|
||||
@ -36,6 +36,42 @@ const Color _kLightThemeSplashColor = const Color(0x66C8C8C8);
|
||||
const Color _kDarkThemeHighlightColor = const Color(0x40CCCCCC);
|
||||
const Color _kDarkThemeSplashColor = const Color(0x40CCCCCC);
|
||||
|
||||
/// Configures the tap target and layout size of certain Material widgets.
|
||||
///
|
||||
/// Changing the value in [ThemeData.materialTapTargetSize] will affect the
|
||||
/// accessibility experience.
|
||||
///
|
||||
/// Some of the impacted widgets include:
|
||||
///
|
||||
/// * [FloatingActionButton], only the mini tap target size is increased.
|
||||
/// * [MaterialButton]
|
||||
/// * [OutlineButton]
|
||||
/// * [FlatButton]
|
||||
/// * [RaisedButton]
|
||||
/// * [TimePicker]
|
||||
/// * [SnackBar]
|
||||
/// * [Chip]
|
||||
/// * [RawChip]
|
||||
/// * [InputChip]
|
||||
/// * [ChoiceChip]
|
||||
/// * [FilterChip]
|
||||
/// * [ActionChip]
|
||||
/// * [Radio]
|
||||
/// * [Switch]
|
||||
/// * [Checkbox]
|
||||
enum MaterialTapTargetSize {
|
||||
/// Expands the minimum tap target size to 48px by 48px.
|
||||
///
|
||||
/// This is the default value of [ThemeData.materialHitTestSize] and the
|
||||
/// recommended size to conform to Android accessibility scanner
|
||||
/// recommendations.
|
||||
padded,
|
||||
|
||||
/// Shrinks the tap target size to the minimum provided by the Material
|
||||
/// specification.
|
||||
shrinkWrap,
|
||||
}
|
||||
|
||||
/// Holds the color and typography values for a material design theme.
|
||||
///
|
||||
/// Use this class to configure a [Theme] widget.
|
||||
@ -105,7 +141,9 @@ class ThemeData extends Diagnosticable {
|
||||
SliderThemeData sliderTheme,
|
||||
ChipThemeData chipTheme,
|
||||
TargetPlatform platform,
|
||||
MaterialTapTargetSize materialTapTargetSize,
|
||||
}) {
|
||||
materialTapTargetSize ??= MaterialTapTargetSize.padded;
|
||||
brightness ??= Brightness.light;
|
||||
final bool isDark = brightness == Brightness.dark;
|
||||
primarySwatch ??= Colors.blue;
|
||||
@ -208,6 +246,7 @@ class ThemeData extends Diagnosticable {
|
||||
sliderTheme: sliderTheme,
|
||||
chipTheme: chipTheme,
|
||||
platform: platform,
|
||||
materialTapTargetSize: materialTapTargetSize,
|
||||
);
|
||||
}
|
||||
|
||||
@ -257,6 +296,7 @@ class ThemeData extends Diagnosticable {
|
||||
@required this.sliderTheme,
|
||||
@required this.chipTheme,
|
||||
@required this.platform,
|
||||
@required this.materialTapTargetSize,
|
||||
}) : assert(brightness != null),
|
||||
assert(primaryColor != null),
|
||||
assert(primaryColorBrightness != null),
|
||||
@ -294,7 +334,8 @@ class ThemeData extends Diagnosticable {
|
||||
assert(accentIconTheme != null),
|
||||
assert(sliderTheme != null),
|
||||
assert(chipTheme != null),
|
||||
assert(platform != null);
|
||||
assert(platform != null),
|
||||
assert(materialTapTargetSize != null);
|
||||
|
||||
/// A default light blue theme.
|
||||
///
|
||||
@ -483,6 +524,9 @@ class ThemeData extends Diagnosticable {
|
||||
/// Defaults to the current platform.
|
||||
final TargetPlatform platform;
|
||||
|
||||
/// Configures the hit test size of certain Material widgets.
|
||||
final MaterialTapTargetSize materialTapTargetSize;
|
||||
|
||||
/// Creates a copy of this theme but with the given fields replaced with the new values.
|
||||
ThemeData copyWith({
|
||||
Brightness brightness,
|
||||
@ -524,6 +568,7 @@ class ThemeData extends Diagnosticable {
|
||||
SliderThemeData sliderTheme,
|
||||
ChipThemeData chipTheme,
|
||||
TargetPlatform platform,
|
||||
MaterialTapTargetSize materialTapTargetSize,
|
||||
}) {
|
||||
return new ThemeData.raw(
|
||||
brightness: brightness ?? this.brightness,
|
||||
@ -565,6 +610,7 @@ class ThemeData extends Diagnosticable {
|
||||
sliderTheme: sliderTheme ?? this.sliderTheme,
|
||||
chipTheme: chipTheme ?? this.chipTheme,
|
||||
platform: platform ?? this.platform,
|
||||
materialTapTargetSize: materialTapTargetSize ?? this.materialTapTargetSize,
|
||||
);
|
||||
}
|
||||
|
||||
@ -692,6 +738,7 @@ class ThemeData extends Diagnosticable {
|
||||
sliderTheme: SliderThemeData.lerp(a.sliderTheme, b.sliderTheme, t),
|
||||
chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t),
|
||||
platform: t < 0.5 ? a.platform : b.platform,
|
||||
materialTapTargetSize: t < 0.5 ? a.materialTapTargetSize : b.materialTapTargetSize,
|
||||
);
|
||||
}
|
||||
|
||||
@ -736,7 +783,8 @@ class ThemeData extends Diagnosticable {
|
||||
(otherData.accentIconTheme == accentIconTheme) &&
|
||||
(otherData.sliderTheme == sliderTheme) &&
|
||||
(otherData.chipTheme == chipTheme) &&
|
||||
(otherData.platform == platform);
|
||||
(otherData.platform == platform) &&
|
||||
(otherData.materialTapTargetSize == materialTapTargetSize);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -780,6 +828,7 @@ class ThemeData extends Diagnosticable {
|
||||
sliderTheme,
|
||||
chipTheme,
|
||||
platform,
|
||||
materialTapTargetSize
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -824,6 +873,7 @@ class ThemeData extends Diagnosticable {
|
||||
properties.add(new DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme));
|
||||
properties.add(new DiagnosticsProperty<SliderThemeData>('sliderTheme', sliderTheme));
|
||||
properties.add(new DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme));
|
||||
properties.add(new DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ import 'feedback.dart';
|
||||
import 'flat_button.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
import 'time.dart';
|
||||
import 'typography.dart';
|
||||
|
||||
@ -29,11 +30,15 @@ enum _TimePickerMode { hour, minute }
|
||||
const double _kTimePickerHeaderPortraitHeight = 96.0;
|
||||
const double _kTimePickerHeaderLandscapeWidth = 168.0;
|
||||
|
||||
|
||||
const double _kTimePickerWidthPortrait = 328.0;
|
||||
const double _kTimePickerWidthLandscape = 512.0;
|
||||
|
||||
const double _kTimePickerHeightPortrait = 484.0;
|
||||
const double _kTimePickerHeightLandscape = 304.0;
|
||||
const double _kTimePickerHeightPortrait = 496.0;
|
||||
const double _kTimePickerHeightLandscape = 316.0;
|
||||
|
||||
const double _kTimePickerHeightPortraitCollapsed = 484.0;
|
||||
const double _kTimePickerHeightLandscapeCollapsed = 304.0;
|
||||
|
||||
/// The horizontal gap between the day period fragment and the fragment
|
||||
/// positioned next to it horizontally.
|
||||
@ -1575,12 +1580,25 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
|
||||
),
|
||||
);
|
||||
|
||||
double timePickerHeightPortrait;
|
||||
double timePickerHeightLandscape;
|
||||
switch (theme.materialTapTargetSize) {
|
||||
case MaterialTapTargetSize.padded:
|
||||
timePickerHeightPortrait = _kTimePickerHeightPortrait;
|
||||
timePickerHeightLandscape = _kTimePickerHeightLandscape;
|
||||
break;
|
||||
case MaterialTapTargetSize.shrinkWrap:
|
||||
timePickerHeightPortrait = _kTimePickerHeightPortraitCollapsed;
|
||||
timePickerHeightLandscape = _kTimePickerHeightLandscapeCollapsed;
|
||||
break;
|
||||
}
|
||||
|
||||
assert(orientation != null);
|
||||
switch (orientation) {
|
||||
case Orientation.portrait:
|
||||
return new SizedBox(
|
||||
width: _kTimePickerWidthPortrait,
|
||||
height: _kTimePickerHeightPortrait,
|
||||
height: timePickerHeightPortrait,
|
||||
child: new Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
@ -1595,7 +1613,7 @@ class _TimePickerDialogState extends State<_TimePickerDialog> {
|
||||
case Orientation.landscape:
|
||||
return new SizedBox(
|
||||
width: _kTimePickerWidthLandscape,
|
||||
height: _kTimePickerHeightLandscape,
|
||||
height: timePickerHeightLandscape,
|
||||
child: new Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
||||
@ -26,10 +26,10 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
RenderToggleable({
|
||||
@required bool value,
|
||||
bool tristate = false,
|
||||
Size size,
|
||||
@required Color activeColor,
|
||||
@required Color inactiveColor,
|
||||
ValueChanged<bool> onChanged,
|
||||
BoxConstraints additionalConstraints,
|
||||
@required TickerProvider vsync,
|
||||
}) : assert(tristate != null),
|
||||
assert(tristate || value != null),
|
||||
@ -42,7 +42,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
_inactiveColor = inactiveColor,
|
||||
_onChanged = onChanged,
|
||||
_vsync = vsync,
|
||||
super(additionalConstraints: new BoxConstraints.tight(size)) {
|
||||
super(additionalConstraints: additionalConstraints) {
|
||||
_tap = new TapGestureRecognizer()
|
||||
..onTapDown = _handleTapDown
|
||||
..onTap = _handleTap
|
||||
|
||||
@ -39,8 +39,8 @@ void main() {
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
label: 'ABC',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0),
|
||||
transform: new Matrix4.translationValues(356.0, 282.0, 0.0),
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
|
||||
transform: new Matrix4.translationValues(356.0, 276.0, 0.0),
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
@ -79,8 +79,8 @@ void main() {
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
label: 'ABC',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0),
|
||||
transform: new Matrix4.translationValues(356.0, 282.0, 0.0),
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
|
||||
transform: new Matrix4.translationValues(356.0, 276.0, 0.0),
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
@ -113,7 +113,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 36.0)));
|
||||
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 48.0)));
|
||||
expect(tester.getSize(find.byType(Text)), equals(const Size(42.0, 14.0)));
|
||||
|
||||
// textScaleFactor expands text, but not button.
|
||||
@ -134,7 +134,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 36.0)));
|
||||
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 48.0)));
|
||||
// Scaled text rendering is different on Linux and Mac by one pixel.
|
||||
// TODO(#12357): Update this test when text rendering is fixed.
|
||||
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[54.0, 55.0]));
|
||||
@ -162,7 +162,7 @@ void main() {
|
||||
// Scaled text rendering is different on Linux and Mac by one pixel.
|
||||
// TODO(#12357): Update this test when text rendering is fixed.
|
||||
expect(tester.getSize(find.byType(FlatButton)).width, isIn(<double>[158.0, 159.0]));
|
||||
expect(tester.getSize(find.byType(FlatButton)).height, equals(42.0));
|
||||
expect(tester.getSize(find.byType(FlatButton)).height, equals(48.0));
|
||||
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[126.0, 127.0]));
|
||||
expect(tester.getSize(find.byType(Text)).height, equals(42.0));
|
||||
});
|
||||
@ -187,7 +187,9 @@ void main() {
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Theme(
|
||||
data: new ThemeData(),
|
||||
data: new ThemeData(
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
child: buttonWidget,
|
||||
),
|
||||
),
|
||||
@ -233,6 +235,7 @@ void main() {
|
||||
data: new ThemeData(
|
||||
highlightColor: themeHighlightColor1,
|
||||
splashColor: themeSplashColor1,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
child: buttonWidget,
|
||||
),
|
||||
@ -260,6 +263,7 @@ void main() {
|
||||
data: new ThemeData(
|
||||
highlightColor: themeHighlightColor2,
|
||||
splashColor: themeSplashColor2,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
child: buttonWidget, // same widget, so does not get updated because of us
|
||||
),
|
||||
@ -279,7 +283,7 @@ void main() {
|
||||
testWidgets('Disabled MaterialButton has same semantic size as enabled and exposes disabled semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
final Rect expectedButtonSize = new Rect.fromLTRB(0.0, 0.0, 116.0, 36.0);
|
||||
final Rect expectedButtonSize = new Rect.fromLTRB(0.0, 0.0, 116.0, 48.0);
|
||||
// Button is in center of screen
|
||||
final Matrix4 expectedButtonTransform = new Matrix4.identity()
|
||||
..translate(
|
||||
@ -354,4 +358,136 @@ void main() {
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('MaterialButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
final Key key1 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new MaterialButton(
|
||||
key: key1,
|
||||
child: const SizedBox(width: 50.0, height: 8.0),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key1)), const Size(88.0, 48.0));
|
||||
|
||||
final Key key2 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new MaterialButton(
|
||||
key: key2,
|
||||
child: const SizedBox(width: 50.0, height: 8.0),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key2)), const Size(88.0, 36.0));
|
||||
});
|
||||
|
||||
testWidgets('FlatButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
final Key key1 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new FlatButton(
|
||||
key: key1,
|
||||
child: const SizedBox(width: 50.0, height: 8.0),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key1)), const Size(88.0, 48.0));
|
||||
|
||||
final Key key2 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new FlatButton(
|
||||
key: key2,
|
||||
child: const SizedBox(width: 50.0, height: 8.0),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key2)), const Size(88.0, 36.0));
|
||||
});
|
||||
|
||||
testWidgets('RaisedButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
final Key key1 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new RaisedButton(
|
||||
key: key1,
|
||||
child: const SizedBox(width: 50.0, height: 8.0),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key1)), const Size(88.0, 48.0));
|
||||
|
||||
final Key key2 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new RaisedButton(
|
||||
key: key2,
|
||||
child: const SizedBox(width: 50.0, height: 8.0),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key2)), const Size(88.0, 36.0));
|
||||
});
|
||||
}
|
||||
|
||||
@ -16,13 +16,38 @@ void main() {
|
||||
debugResetSemanticsIdCounter();
|
||||
});
|
||||
|
||||
testWidgets('Checkbox size is 40x40', (WidgetTester tester) async {
|
||||
testWidgets('Checkbox size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Material(
|
||||
child: new Center(
|
||||
child: new Checkbox(
|
||||
value: false,
|
||||
onChanged: (bool newValue) { },
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new Checkbox(
|
||||
value: true,
|
||||
onChanged: (bool newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byType(Checkbox)), const Size(48.0, 48.0));
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new Checkbox(
|
||||
value: true,
|
||||
onChanged: (bool newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -4,11 +4,13 @@
|
||||
|
||||
import 'dart:ui' show window;
|
||||
|
||||
import 'package:flutter/semantics.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import '../widgets/semantics_tester.dart';
|
||||
import 'feedback_tester.dart';
|
||||
|
||||
Finder findRenderChipElement() {
|
||||
@ -249,7 +251,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
|
||||
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 32.0));
|
||||
expect(tester.getSize(find.byType(Chip)), const Size(64.0,48.0));
|
||||
await tester.pumpWidget(
|
||||
_wrapForChip(
|
||||
child: new Row(
|
||||
@ -260,7 +262,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
|
||||
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 32.0));
|
||||
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 48.0));
|
||||
await tester.pumpWidget(
|
||||
_wrapForChip(
|
||||
child: new Row(
|
||||
@ -271,7 +273,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
|
||||
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 32.0));
|
||||
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 48.0));
|
||||
});
|
||||
|
||||
testWidgets('Chip elements are ordered horizontally for locale', (WidgetTester tester) async {
|
||||
@ -338,8 +340,8 @@ void main() {
|
||||
tester.getSize(find.text('Chip B')),
|
||||
anyOf(const Size(84.0, 14.0), const Size(83.0, 14.0)),
|
||||
);
|
||||
expect(tester.getSize(find.byType(Chip).first), anyOf(const Size(132.0, 32.0), const Size(131.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 32.0), const Size(131.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(Chip).first), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0)));
|
||||
expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0)));
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrapForChip(
|
||||
@ -392,7 +394,7 @@ void main() {
|
||||
expect(tester.getSize(find.text('Chip B')), anyOf(const Size(84.0, 14.0), const Size(83.0, 14.0)));
|
||||
expect(tester.getSize(find.byType(Chip).first).width, anyOf(318.0, 319.0));
|
||||
expect(tester.getSize(find.byType(Chip).first).height, equals(50.0));
|
||||
expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 32.0), const Size(131.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(Chip).last), anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0)));
|
||||
});
|
||||
|
||||
testWidgets('Labels can be non-text widgets', (WidgetTester tester) async {
|
||||
@ -424,9 +426,9 @@ void main() {
|
||||
expect(tester.getSize(find.byKey(keyB)), const Size(10.0, 10.0));
|
||||
expect(
|
||||
tester.getSize(find.byType(Chip).first),
|
||||
anyOf(const Size(132.0, 32.0), const Size(131.0, 32.0)),
|
||||
anyOf(const Size(132.0, 48.0), const Size(131.0, 48.0)),
|
||||
);
|
||||
expect(tester.getSize(find.byType(Chip).last), const Size(58.0, 32.0));
|
||||
expect(tester.getSize(find.byType(Chip).last), const Size(58.0, 48.0));
|
||||
});
|
||||
|
||||
testWidgets('Avatars can be non-circle avatar widgets', (WidgetTester tester) async {
|
||||
@ -561,7 +563,7 @@ void main() {
|
||||
|
||||
// No avatar
|
||||
await pushChip();
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
||||
final GlobalKey avatarKey = new GlobalKey();
|
||||
|
||||
// Add an avatar
|
||||
@ -574,10 +576,10 @@ void main() {
|
||||
),
|
||||
);
|
||||
// Avatar drawer should start out closed.
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
||||
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(-20.0, 4.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(-20.0, 12.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
// Avatar drawer should start expanding.
|
||||
@ -607,18 +609,18 @@ void main() {
|
||||
// Wait for being done with animation, and make sure it didn't change
|
||||
// height.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
||||
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 4.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 12.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 17.0)));
|
||||
|
||||
// Remove the avatar again
|
||||
await pushChip();
|
||||
// Avatar drawer should start out open.
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
||||
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 4.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 12.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 17.0)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
// Avatar drawer should start contracting.
|
||||
@ -648,8 +650,8 @@ void main() {
|
||||
// Wait for being done with animation, make sure it didn't change
|
||||
// height, and make sure that the avatar is no longer drawn.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 32.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
expect(find.byKey(avatarKey), findsNothing);
|
||||
});
|
||||
|
||||
@ -684,22 +686,22 @@ void main() {
|
||||
|
||||
// No delete button
|
||||
await pushChip();
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
||||
|
||||
// Add a delete button
|
||||
await pushChip(deletable: true);
|
||||
// Delete button drawer should start out closed.
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
||||
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(52.0, 4.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(52.0, 12.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
// Delete button drawer should start expanding.
|
||||
expect(tester.getSize(find.byType(RawChip)).width, closeTo(81.2, 0.1));
|
||||
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, closeTo(53.2, 0.1));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
expect(tester.getSize(find.byType(RawChip)).width, closeTo(86.7, 0.1));
|
||||
@ -719,10 +721,10 @@ void main() {
|
||||
// Wait for being done with animation, and make sure it didn't change
|
||||
// height.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
||||
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 4.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 12.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
|
||||
// Test the tap work for the delete button, but not the rest of the chip.
|
||||
expect(wasDeleted, isFalse);
|
||||
@ -734,17 +736,17 @@ void main() {
|
||||
// Remove the delete button again
|
||||
await pushChip();
|
||||
// Delete button drawer should start out open.
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
||||
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 4.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 12.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
// Delete button drawer should start contracting.
|
||||
expect(tester.getSize(find.byType(RawChip)).width, closeTo(103.8, 0.1));
|
||||
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
||||
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, closeTo(75.8, 0.1));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 20));
|
||||
expect(tester.getSize(find.byType(RawChip)).width, closeTo(102.9, 0.1));
|
||||
@ -764,8 +766,8 @@ void main() {
|
||||
// Wait for being done with animation, make sure it didn't change
|
||||
// height, and make sure that the delete button is no longer drawn.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 32.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 9.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
||||
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
||||
expect(find.byKey(deleteButtonKey), findsNothing);
|
||||
});
|
||||
|
||||
@ -806,7 +808,7 @@ void main() {
|
||||
await pushChip(
|
||||
avatar: new Container(width: 40.0, height: 40.0, key: avatarKey),
|
||||
);
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
||||
|
||||
// Turn on selection.
|
||||
await pushChip(
|
||||
@ -885,7 +887,7 @@ void main() {
|
||||
|
||||
// Without avatar, but not selectable.
|
||||
await pushChip();
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 32.0)));
|
||||
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
||||
|
||||
// Turn on selection.
|
||||
await pushChip(selectable: true);
|
||||
@ -1017,6 +1019,42 @@ void main() {
|
||||
expect(materialBox, paints..path(color: chipTheme.disabledColor));
|
||||
});
|
||||
|
||||
testWidgets('Chip size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
final Key key1 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
_wrapForChip(
|
||||
child: new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Center(
|
||||
child: new RawChip(
|
||||
key: key1,
|
||||
label: const Text('test'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key1)), const Size(80.0, 48.0));
|
||||
|
||||
final Key key2 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
_wrapForChip(
|
||||
child: new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Center(
|
||||
child: new RawChip(
|
||||
key: key2,
|
||||
label: const Text('test'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key2)), const Size(80.0, 32.0));
|
||||
});
|
||||
|
||||
testWidgets('Chip uses the right theme colors for the right components', (WidgetTester tester) async {
|
||||
final ThemeData themeData = new ThemeData(
|
||||
platform: TargetPlatform.android,
|
||||
@ -1138,4 +1176,255 @@ void main() {
|
||||
expect(materialBox, paints..path(color: customTheme.disabledColor));
|
||||
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
||||
});
|
||||
|
||||
group('Chip semantics', () {
|
||||
testWidgets('label only', (WidgetTester tester) async {
|
||||
final SemanticsTester semanticsTester = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: const Material(
|
||||
child: const RawChip(
|
||||
label: const Text('test'),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(semanticsTester, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
semanticsTester.dispose();
|
||||
});
|
||||
|
||||
testWidgets('with onPressed', (WidgetTester tester) async {
|
||||
final SemanticsTester semanticsTester = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new RawChip(
|
||||
label: const Text('test'),
|
||||
onPressed: () {},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(semanticsTester, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
|
||||
semanticsTester.dispose();
|
||||
});
|
||||
|
||||
|
||||
testWidgets('with onSelected', (WidgetTester tester) async {
|
||||
final SemanticsTester semanticsTester = new SemanticsTester(tester);
|
||||
bool selected = false;
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new RawChip(
|
||||
isEnabled: true,
|
||||
label: const Text('test'),
|
||||
selected: selected,
|
||||
onSelected: (bool value) {
|
||||
selected = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(semanticsTester, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
|
||||
await tester.tap(find.byType(RawChip));
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new RawChip(
|
||||
isEnabled: true,
|
||||
label: const Text('test'),
|
||||
selected: selected,
|
||||
onSelected: (bool value) {
|
||||
selected = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(selected, true);
|
||||
expect(semanticsTester, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isSelected,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
SemanticsFlag.isEnabled,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
|
||||
semanticsTester.dispose();
|
||||
});
|
||||
|
||||
testWidgets('disabled', (WidgetTester tester) async {
|
||||
final SemanticsTester semanticsTester = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(
|
||||
home: new Material(
|
||||
child: new RawChip(
|
||||
isEnabled: false,
|
||||
onPressed: () {},
|
||||
label: const Text('test'),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(semanticsTester, hasSemantics(
|
||||
new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
textDirection: TextDirection.ltr,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
label: 'test',
|
||||
textDirection: TextDirection.ltr,
|
||||
flags: <SemanticsFlag>[],
|
||||
actions: <SemanticsAction>[],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
), ignoreTransform: true, ignoreId: true, ignoreRect: true));
|
||||
|
||||
semanticsTester.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
testWidgets('can be tapped outside of chip delete icon', (WidgetTester tester) async {
|
||||
bool deleted = false;
|
||||
await tester.pumpWidget(
|
||||
_wrapForChip(
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new Chip(
|
||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||
shape: const RoundedRectangleBorder(borderRadius: const BorderRadius.all(const Radius.circular(0.0))),
|
||||
avatar: const CircleAvatar(child: const Text('A')),
|
||||
label: const Text('Chip A'),
|
||||
onDeleted: () {
|
||||
deleted = true;
|
||||
},
|
||||
deleteIcon: const Icon(Icons.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapAt(tester.getTopRight(find.byType(Chip)) - const Offset(2.0, -2.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(deleted, true);
|
||||
});
|
||||
|
||||
testWidgets('can be tapped outside of chip body', (WidgetTester tester) async {
|
||||
bool pressed = false;
|
||||
await tester.pumpWidget(
|
||||
_wrapForChip(
|
||||
child: new Row(
|
||||
children: <Widget>[
|
||||
new InputChip(
|
||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||
shape: const RoundedRectangleBorder(borderRadius: const BorderRadius.all(const Radius.circular(0.0))),
|
||||
avatar: const CircleAvatar(child: const Text('A')),
|
||||
label: const Text('Chip A'),
|
||||
onPressed: () {
|
||||
pressed = true;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tapAt(tester.getRect(find.byType(InputChip)).topCenter);
|
||||
await tester.pumpAndSettle();
|
||||
expect(pressed, true);
|
||||
});
|
||||
}
|
||||
|
||||
@ -67,6 +67,44 @@ void main() {
|
||||
expect(find.byType(Text), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('FlatActionButton mini size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
final Key key1 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Scaffold(
|
||||
floatingActionButton: new FloatingActionButton(
|
||||
key: key1,
|
||||
mini: true,
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key1)), const Size(48.0, 48.0));
|
||||
|
||||
final Key key2 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
home: new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Scaffold(
|
||||
floatingActionButton: new FloatingActionButton(
|
||||
key: key2,
|
||||
mini: true,
|
||||
onPressed: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key2)), const Size(40.0, 40.0));
|
||||
});
|
||||
|
||||
testWidgets('FloatingActionButton.isExtended', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new MaterialApp(
|
||||
|
||||
@ -55,7 +55,7 @@ void main() {
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Theme(
|
||||
data: new ThemeData(),
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Container(
|
||||
alignment: Alignment.topLeft,
|
||||
child: new OutlineButton(
|
||||
@ -140,8 +140,8 @@ void main() {
|
||||
SemanticsAction.tap,
|
||||
],
|
||||
label: 'ABC',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0),
|
||||
transform: new Matrix4.translationValues(356.0, 282.0, 0.0),
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
|
||||
transform: new Matrix4.translationValues(356.0, 276.0, 0.0),
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isButton,
|
||||
SemanticsFlag.hasEnabledState,
|
||||
@ -175,7 +175,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byType(OutlineButton)), equals(const Size(88.0, 36.0)));
|
||||
expect(tester.getSize(find.byType(OutlineButton)), equals(const Size(88.0, 48.0)));
|
||||
expect(tester.getSize(find.byType(Text)), equals(const Size(42.0, 14.0)));
|
||||
|
||||
// textScaleFactor expands text, but not button.
|
||||
@ -196,7 +196,7 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 36.0)));
|
||||
expect(tester.getSize(find.byType(FlatButton)), equals(const Size(88.0, 48.0)));
|
||||
// Scaled text rendering is different on Linux and Mac by one pixel.
|
||||
// TODO(#12357): Update this test when text rendering is fixed.
|
||||
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[54.0, 55.0]));
|
||||
@ -224,7 +224,7 @@ void main() {
|
||||
// Scaled text rendering is different on Linux and Mac by one pixel.
|
||||
// TODO(#12357): Update this test when text rendering is fixed.
|
||||
expect(tester.getSize(find.byType(FlatButton)).width, isIn(<double>[158.0, 159.0]));
|
||||
expect(tester.getSize(find.byType(FlatButton)).height, equals(42.0));
|
||||
expect(tester.getSize(find.byType(FlatButton)).height, equals(48.0));
|
||||
expect(tester.getSize(find.byType(Text)).width, isIn(<double>[126.0, 127.0]));
|
||||
expect(tester.getSize(find.byType(Text)).height, equals(42.0));
|
||||
});
|
||||
|
||||
@ -64,23 +64,50 @@ void main() {
|
||||
expect(log, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('Radio size is 40x40', (WidgetTester tester) async {
|
||||
final Key key = new UniqueKey();
|
||||
|
||||
testWidgets('Radio size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
final Key key1 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Material(
|
||||
child: new Center(
|
||||
child: new Radio<int>(
|
||||
key: key,
|
||||
value: 1,
|
||||
groupValue: 2,
|
||||
onChanged: (int newValue) { },
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new Radio<bool>(
|
||||
key: key1,
|
||||
groupValue: true,
|
||||
value: true,
|
||||
onChanged: (bool newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key)), const Size(40.0, 40.0));
|
||||
expect(tester.getSize(find.byKey(key1)), const Size(48.0, 48.0));
|
||||
|
||||
final Key key2 = new UniqueKey();
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new Radio<bool>(
|
||||
key: key2,
|
||||
groupValue: true,
|
||||
value: true,
|
||||
onChanged: (bool newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byKey(key2)), const Size(40.0, 40.0));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@ -2,10 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('outerPadding expands hit test area', (WidgetTester tester) async {
|
||||
testWidgets('materialTapTargetSize.padded expands hit test area', (WidgetTester tester) async {
|
||||
int pressed = 0;
|
||||
|
||||
await tester.pumpWidget(new RawMaterialButton(
|
||||
@ -13,23 +14,23 @@ void main() {
|
||||
pressed++;
|
||||
},
|
||||
constraints: new BoxConstraints.tight(const Size(10.0, 10.0)),
|
||||
outerPadding: const EdgeInsets.all(50.0),
|
||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||
child: const Text('+', textDirection: TextDirection.ltr),
|
||||
));
|
||||
|
||||
await tester.tapAt(const Offset(100.0, 100.0));
|
||||
await tester.tapAt(const Offset(40.0, 400.0));
|
||||
|
||||
expect(pressed, 1);
|
||||
});
|
||||
|
||||
testWidgets('outerPadding expands semantics area', (WidgetTester tester) async {
|
||||
testWidgets('materialTapTargetSize.padded expands semantics area', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
new Center(
|
||||
child: new RawMaterialButton(
|
||||
onPressed: () {},
|
||||
constraints: new BoxConstraints.tight(const Size(10.0, 10.0)),
|
||||
outerPadding: const EdgeInsets.all(50.0),
|
||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||
child: const Text('+', textDirection: TextDirection.ltr),
|
||||
),
|
||||
),
|
||||
@ -50,7 +51,7 @@ void main() {
|
||||
],
|
||||
label: '+',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: Rect.fromLTRB(0.0, 0.0, 110.0, 110.0),
|
||||
rect: Rect.fromLTRB(0.0, 0.0, 48.0, 48.0),
|
||||
children: <TestSemantics>[],
|
||||
),
|
||||
]
|
||||
@ -58,4 +59,57 @@ void main() {
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Ink splash from center tap originates in correct location', (WidgetTester tester) async {
|
||||
const Color highlightColor = const Color(0xAAFF0000);
|
||||
const Color splashColor = const Color(0xAA0000FF);
|
||||
const Color fillColor = const Color(0xFFEF5350);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new RawMaterialButton(
|
||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||
onPressed: () {},
|
||||
fillColor: fillColor,
|
||||
highlightColor: highlightColor,
|
||||
splashColor: splashColor,
|
||||
child: const SizedBox(),
|
||||
)
|
||||
);
|
||||
|
||||
final Offset center = tester.getCenter(find.byType(InkWell));
|
||||
final TestGesture gesture = await tester.startGesture(center);
|
||||
await tester.pump(); // start gesture
|
||||
await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
|
||||
|
||||
final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as dynamic;
|
||||
// centered in material button.
|
||||
expect(box, paints..circle(x: 44.0, y: 18.0, color: splashColor));
|
||||
await gesture.up();
|
||||
});
|
||||
|
||||
testWidgets('Ink splash from tap above material originates in correct location', (WidgetTester tester) async {
|
||||
const Color highlightColor = const Color(0xAAFF0000);
|
||||
const Color splashColor = const Color(0xAA0000FF);
|
||||
const Color fillColor = const Color(0xFFEF5350);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new RawMaterialButton(
|
||||
materialTapTargetSize: MaterialTapTargetSize.padded,
|
||||
onPressed: () {},
|
||||
fillColor: fillColor,
|
||||
highlightColor: highlightColor,
|
||||
splashColor: splashColor,
|
||||
child: const SizedBox(),
|
||||
)
|
||||
);
|
||||
|
||||
final Offset top = tester.getRect(find.byType(InkWell)).topCenter;
|
||||
final TestGesture gesture = await tester.startGesture(top);
|
||||
await tester.pump(); // start gesture
|
||||
await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
|
||||
final RenderBox box = Material.of(tester.element(find.byType(InkWell))) as dynamic;
|
||||
// paints above above material
|
||||
expect(box, paints..circle(x: 44.0, y: 0.0, color: splashColor));
|
||||
await gesture.up();
|
||||
});
|
||||
}
|
||||
@ -542,7 +542,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
));
|
||||
expect(tester.element(find.byKey(testKey)).size, const Size(88.0, 36.0));
|
||||
expect(tester.element(find.byKey(testKey)).size, const Size(88.0, 48.0));
|
||||
expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0));
|
||||
});
|
||||
});
|
||||
|
||||
@ -341,10 +341,10 @@ void main() {
|
||||
final Offset snackBarBottomRight = snackBarBox.localToGlobal(snackBarBox.size.bottomRight(Offset.zero));
|
||||
|
||||
expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding
|
||||
expect(snackBarBottomLeft.dy - textBottomLeft.dy, 14.0 + 40.0); // margin + bottom padding
|
||||
expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0 + 40.0); // margin + bottom padding
|
||||
expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0);
|
||||
expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding
|
||||
expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 14.0 + 40.0); // margin + bottom padding
|
||||
expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0 + 40.0); // margin + bottom padding
|
||||
});
|
||||
|
||||
testWidgets('SnackBar is positioned above BottomNavigationBar', (WidgetTester tester) async {
|
||||
@ -398,10 +398,10 @@ void main() {
|
||||
final Offset snackBarBottomRight = snackBarBox.localToGlobal(snackBarBox.size.bottomRight(Offset.zero));
|
||||
|
||||
expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding
|
||||
expect(snackBarBottomLeft.dy - textBottomLeft.dy, 14.0); // margin (with no bottom padding)
|
||||
expect(snackBarBottomLeft.dy - textBottomLeft.dy, 17.0); // margin (with no bottom padding)
|
||||
expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0);
|
||||
expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 30.0); // margin + right padding
|
||||
expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 14.0); // margin (with no bottom padding)
|
||||
expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0); // margin (with no bottom padding)
|
||||
});
|
||||
|
||||
testWidgets('SnackBarClosedReason', (WidgetTester tester) async {
|
||||
|
||||
@ -43,6 +43,46 @@ void main() {
|
||||
expect(value, isTrue);
|
||||
});
|
||||
|
||||
testWidgets('Switch size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new Switch(
|
||||
value: true,
|
||||
onChanged: (bool newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 48.0));
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Theme(
|
||||
data: new ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
||||
child: new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Material(
|
||||
child: new Center(
|
||||
child: new Switch(
|
||||
value: true,
|
||||
onChanged: (bool newValue) {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0));
|
||||
});
|
||||
|
||||
testWidgets('Switch can drag (LTR)', (WidgetTester tester) async {
|
||||
bool value = false;
|
||||
|
||||
|
||||
@ -103,6 +103,12 @@ void main() {
|
||||
expect(darkTheme.accentTextTheme.title.color, typography.white.title.color);
|
||||
});
|
||||
|
||||
test('Defaults to MaterialTapTargetBehavior.expanded', () {
|
||||
final ThemeData themeData = new ThemeData();
|
||||
|
||||
expect(themeData.materialTapTargetSize, MaterialTapTargetSize.padded);
|
||||
});
|
||||
|
||||
test('Can control fontFamily', () {
|
||||
final ThemeData themeData = new ThemeData(fontFamily: 'Ahem');
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user