Add Density API to ThemeData, implement for buttons. (#43547)

* Add a density attribute to ThemeData

* Simplify tests

* Review changes (Hans)
This commit is contained in:
Greg Spencer 2019-12-03 19:49:56 -08:00 committed by GitHub
parent 1ac17c14fc
commit 185da9b0af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 535 additions and 9 deletions

View file

@ -52,6 +52,7 @@ class RawMaterialButton extends StatefulWidget {
this.highlightElevation = 8.0,
this.disabledElevation = 0.0,
this.padding = EdgeInsets.zero,
this.visualDensity = const VisualDensity(),
this.constraints = const BoxConstraints(minWidth: 88.0, minHeight: 36.0),
this.shape = const RoundedRectangleBorder(),
this.animationDuration = kThemeChangeDuration,
@ -207,6 +208,16 @@ class RawMaterialButton extends StatefulWidget {
/// The internal padding for the button's [child].
final EdgeInsetsGeometry padding;
/// Defines how compact the button's layout will be.
///
/// {@macro flutter.material.themedata.visualDensity}
///
/// See also:
///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all widgets
/// within a [Theme].
final VisualDensity visualDensity;
/// Defines the button's size.
///
/// Typically used to constrain the button's minimum size.
@ -354,9 +365,22 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
Widget build(BuildContext context) {
final Color effectiveTextColor = MaterialStateProperty.resolveAs<Color>(widget.textStyle?.color, _states);
final ShapeBorder effectiveShape = MaterialStateProperty.resolveAs<ShapeBorder>(widget.shape, _states);
final Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
final BoxConstraints effectiveConstraints = widget.constraints.copyWith(
minWidth: widget.constraints.minWidth != null ? (widget.constraints.minWidth + densityAdjustment.dx).clamp(0.0, double.infinity) : null,
minHeight: widget.constraints.minWidth != null ? (widget.constraints.minHeight + densityAdjustment.dy).clamp(0.0, double.infinity) : null,
);
final EdgeInsetsGeometry padding = widget.padding.add(
EdgeInsets.only(
left: densityAdjustment.dx,
top: densityAdjustment.dy,
right: densityAdjustment.dx,
bottom: densityAdjustment.dy,
),
).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity);
final Widget result = ConstrainedBox(
constraints: widget.constraints,
constraints: effectiveConstraints,
child: Material(
elevation: _effectiveElevation,
textStyle: widget.textStyle?.copyWith(color: effectiveTextColor),
@ -383,7 +407,7 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
child: IconTheme.merge(
data: IconThemeData(color: effectiveTextColor),
child: Container(
padding: widget.padding,
padding: padding,
child: Center(
widthFactor: 1.0,
heightFactor: 1.0,
@ -397,7 +421,12 @@ class _RawMaterialButtonState extends State<RawMaterialButton> {
Size minSize;
switch (widget.materialTapTargetSize) {
case MaterialTapTargetSize.padded:
minSize = const Size(48.0, 48.0);
minSize = Size(
kMinInteractiveDimension + densityAdjustment.dx,
kMinInteractiveDimension + densityAdjustment.dy,
);
assert(minSize.width >= 0.0);
assert(minSize.height >= 0.0);
break;
case MaterialTapTargetSize.shrinkWrap:
minSize = Size.zero;

View file

@ -115,6 +115,7 @@ class FlatButton extends MaterialButton {
Color splashColor,
Brightness colorBrightness,
EdgeInsetsGeometry padding,
VisualDensity visualDensity,
ShapeBorder shape,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
@ -139,6 +140,7 @@ class FlatButton extends MaterialButton {
splashColor: splashColor,
colorBrightness: colorBrightness,
padding: padding,
visualDensity: visualDensity,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
@ -199,6 +201,7 @@ class FlatButton extends MaterialButton {
highlightElevation: buttonTheme.getHighlightElevation(this),
disabledElevation: buttonTheme.getDisabledElevation(this),
padding: buttonTheme.getPadding(this),
visualDensity: visualDensity ?? theme.visualDensity,
constraints: buttonTheme.getConstraints(this),
shape: buttonTheme.getShape(this),
clipBehavior: clipBehavior,

View file

@ -69,6 +69,7 @@ class MaterialButton extends StatelessWidget {
this.highlightElevation,
this.disabledElevation,
this.padding,
this.visualDensity,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
@ -311,6 +312,16 @@ class MaterialButton extends StatelessWidget {
/// [ButtonThemeData.padding].
final EdgeInsetsGeometry padding;
/// Defines how compact the button's layout will be.
///
/// {@macro flutter.material.themedata.visualDensity}
///
/// See also:
///
/// * [ThemeData.visualDensity], which specifies the [density] for all widgets
/// within a [Theme].
final VisualDensity visualDensity;
/// The shape of the button's [Material].
///
/// The button's highlight and splash are clipped to this shape. If the
@ -387,6 +398,7 @@ class MaterialButton extends StatelessWidget {
hoverElevation: buttonTheme.getHoverElevation(this),
highlightElevation: buttonTheme.getHighlightElevation(this),
padding: buttonTheme.getPadding(this),
visualDensity: visualDensity ?? theme.visualDensity,
constraints: buttonTheme.getConstraints(this).copyWith(
minWidth: minWidth,
minHeight: height,
@ -416,6 +428,7 @@ class MaterialButton extends StatelessWidget {
properties.add(ColorProperty('splashColor', splashColor, defaultValue: null));
properties.add(DiagnosticsProperty<Brightness>('colorBrightness', colorBrightness, defaultValue: null));
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialTapTargetSize>('materialTapTargetSize', materialTapTargetSize, defaultValue: null));

View file

@ -11,6 +11,7 @@ import 'material_button.dart';
import 'material_state.dart';
import 'raised_button.dart';
import 'theme.dart';
import 'theme_data.dart';
// The total time to make the button's fill color opaque and change
// its elevation. Only applies when highlightElevation > 0.0.
@ -75,6 +76,7 @@ class OutlineButton extends MaterialButton {
this.disabledBorderColor,
this.highlightedBorderColor,
EdgeInsetsGeometry padding,
VisualDensity visualDensity,
ShapeBorder shape,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
@ -97,6 +99,7 @@ class OutlineButton extends MaterialButton {
splashColor: splashColor,
highlightElevation: highlightElevation,
padding: padding,
visualDensity: visualDensity,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
@ -129,6 +132,7 @@ class OutlineButton extends MaterialButton {
Color disabledBorderColor,
BorderSide borderSide,
EdgeInsetsGeometry padding,
VisualDensity visualDensity,
ShapeBorder shape,
Clip clipBehavior,
FocusNode focusNode,
@ -187,6 +191,7 @@ class OutlineButton extends MaterialButton {
disabledBorderColor: disabledBorderColor,
highlightedBorderColor: highlightedBorderColor ?? buttonTheme.colorScheme.primary,
padding: buttonTheme.getPadding(this),
visualDensity: visualDensity,
shape: buttonTheme.getShape(this),
clipBehavior: clipBehavior,
focusNode: focusNode,
@ -225,6 +230,7 @@ class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMi
Color disabledBorderColor,
BorderSide borderSide,
EdgeInsetsGeometry padding,
VisualDensity visualDensity,
ShapeBorder shape,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
@ -253,6 +259,7 @@ class _OutlineButtonWithIcon extends OutlineButton with MaterialButtonWithIconMi
highlightedBorderColor: highlightedBorderColor,
borderSide: borderSide,
padding: padding,
visualDensity: visualDensity,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
@ -287,6 +294,7 @@ class _OutlineButton extends StatefulWidget {
this.disabledBorderColor,
@required this.highlightedBorderColor,
this.padding,
this.visualDensity,
this.shape,
this.clipBehavior = Clip.none,
this.focusNode,
@ -314,6 +322,7 @@ class _OutlineButton extends StatefulWidget {
final Color disabledBorderColor;
final Color highlightedBorderColor;
final EdgeInsetsGeometry padding;
final VisualDensity visualDensity;
final ShapeBorder shape;
final Clip clipBehavior;
final FocusNode focusNode;
@ -435,6 +444,8 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return AnimatedBuilder(
animation: _controller,
builder: (BuildContext context, Widget child) {
@ -456,6 +467,7 @@ class _OutlineButtonState extends State<_OutlineButton> with SingleTickerProvide
highlightElevation: _getHighlightElevation(),
onHighlightChanged: _handleHighlightChanged,
padding: widget.padding,
visualDensity: widget.visualDensity ?? theme.visualDensity,
shape: _OutlineBorder(
shape: widget.shape,
side: _getOutline(),

View file

@ -126,6 +126,7 @@ class RaisedButton extends MaterialButton {
double highlightElevation,
double disabledElevation,
EdgeInsetsGeometry padding,
VisualDensity visualDensity,
ShapeBorder shape,
Clip clipBehavior = Clip.none,
FocusNode focusNode,
@ -161,6 +162,7 @@ class RaisedButton extends MaterialButton {
highlightElevation: highlightElevation,
disabledElevation: disabledElevation,
padding: padding,
visualDensity: visualDensity,
shape: shape,
clipBehavior: clipBehavior,
focusNode: focusNode,
@ -227,6 +229,7 @@ class RaisedButton extends MaterialButton {
highlightElevation: buttonTheme.getHighlightElevation(this),
disabledElevation: buttonTheme.getDisabledElevation(this),
padding: buttonTheme.getPadding(this),
visualDensity: visualDensity ?? theme.visualDensity,
constraints: buttonTheme.getConstraints(this),
shape: buttonTheme.getShape(this),
focusNode: focusNode,

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show Color, hashList;
import 'dart:ui' show Color, hashList, lerpDouble;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
@ -200,6 +200,7 @@ class ThemeData extends Diagnosticable {
/// more discussion on how to pick the right colors.
factory ThemeData({
Brightness brightness,
VisualDensity visualDensity,
MaterialColor primarySwatch,
Color primaryColor,
Brightness primaryColorBrightness,
@ -266,6 +267,7 @@ class ThemeData extends Diagnosticable {
}) {
brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark;
visualDensity ??= const VisualDensity();
primarySwatch ??= Colors.blue;
primaryColor ??= isDark ? Colors.grey[900] : primarySwatch;
primaryColorBrightness ??= estimateBrightnessForColor(primaryColor);
@ -372,6 +374,7 @@ class ThemeData extends Diagnosticable {
return ThemeData.raw(
brightness: brightness,
visualDensity: visualDensity,
primaryColor: primaryColor,
primaryColorBrightness: primaryColorBrightness,
primaryColorLight: primaryColorLight,
@ -448,6 +451,7 @@ class ThemeData extends Diagnosticable {
// operator == and in the hashValues method and in the order of fields
// in this class, and in the lerp() method.
@required this.brightness,
@required this.visualDensity,
@required this.primaryColor,
@required this.primaryColorBrightness,
@required this.primaryColorLight,
@ -510,6 +514,7 @@ class ThemeData extends Diagnosticable {
@required this.dividerTheme,
@required this.buttonBarTheme,
}) : assert(brightness != null),
assert(visualDensity != null),
assert(primaryColor != null),
assert(primaryColorBrightness != null),
assert(primaryColorLight != null),
@ -669,6 +674,35 @@ class ThemeData extends Diagnosticable {
/// dark, use Colors.white or the accentColor for a contrasting color.
final Brightness brightness;
/// The density value for specifying the compactness of various UI components.
///
/// {@template flutter.material.themedata.visualDensity}
/// Density, in the context of a UI, is the vertical and horizontal
/// "compactness" of the elements in the UI. It is unitless, since it means
/// different things to different UI elements. For buttons, it affects the
/// spacing around the centered label of the button. For lists, it affects the
/// distance between baselines of entries in the list.
///
/// Typically, density values are integral, but any value in range may be
/// used. The range includes values from [VisualDensity.minimumDensity] (which
/// is -4), to [VisualDensity.maximumDensity] (which is 4), inclusive, where
/// negative values indicate a denser, more compact, UI, and positive values
/// indicate a less dense, more expanded, UI. If a component doesn't support
/// the value given, it will clamp to the the nearest supported value.
///
/// The default for visual densities is zero for both vertical and horizontal
/// densities, which corresponds to the default visual density of components
/// in the Material Design specification.
///
/// As a rule of thumb, a change of 1 or -1 in density corresponds to 4
/// logical pixels. However, this is not a strict relationship since
/// components interpret the density values appropriately for their needs.
///
/// A larger value translates to a spacing increase (less dense), and a
/// smaller value translates to a spacing decrease (more dense).
/// {@endtemplate}
final VisualDensity visualDensity;
/// The background color for major parts of the app (toolbars, tab bars, etc)
///
/// The theme's [colorScheme] property contains [ColorScheme.primary], as
@ -984,6 +1018,7 @@ class ThemeData extends Diagnosticable {
/// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({
Brightness brightness,
VisualDensity visualDensity,
Color primaryColor,
Brightness primaryColorBrightness,
Color primaryColorLight,
@ -1049,6 +1084,7 @@ class ThemeData extends Diagnosticable {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw(
brightness: brightness ?? this.brightness,
visualDensity: visualDensity ?? this.visualDensity,
primaryColor: primaryColor ?? this.primaryColor,
primaryColorBrightness: primaryColorBrightness ?? this.primaryColorBrightness,
primaryColorLight: primaryColorLight ?? this.primaryColorLight,
@ -1192,6 +1228,7 @@ class ThemeData extends Diagnosticable {
// the class and in the lerp() method.
return ThemeData.raw(
brightness: t < 0.5 ? a.brightness : b.brightness,
visualDensity: VisualDensity.lerp(a.visualDensity, b.visualDensity, t),
primaryColor: Color.lerp(a.primaryColor, b.primaryColor, t),
primaryColorBrightness: t < 0.5 ? a.primaryColorBrightness : b.primaryColorBrightness,
primaryColorLight: Color.lerp(a.primaryColorLight, b.primaryColorLight, t),
@ -1265,6 +1302,7 @@ class ThemeData extends Diagnosticable {
// hashValues() and in the raw constructor and in the order of fields in
// the class and in the lerp() method.
return (otherData.brightness == brightness) &&
(otherData.visualDensity == visualDensity) &&
(otherData.primaryColor == primaryColor) &&
(otherData.primaryColorBrightness == primaryColorBrightness) &&
(otherData.primaryColorLight == primaryColorLight) &&
@ -1333,6 +1371,7 @@ class ThemeData extends Diagnosticable {
// and in the order of fields in the class and in the lerp() method.
final List<Object> values = <Object>[
brightness,
visualDensity,
primaryColor,
primaryColorBrightness,
primaryColorLight,
@ -1404,6 +1443,7 @@ class ThemeData extends Diagnosticable {
final ThemeData defaultData = ThemeData.fallback();
properties.add(EnumProperty<TargetPlatform>('platform', platform, defaultValue: defaultTargetPlatform));
properties.add(EnumProperty<Brightness>('brightness', brightness, defaultValue: defaultData.brightness));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: defaultData.visualDensity));
properties.add(ColorProperty('primaryColor', primaryColor, defaultValue: defaultData.primaryColor));
properties.add(EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, defaultValue: defaultData.primaryColorBrightness));
properties.add(ColorProperty('accentColor', accentColor, defaultValue: defaultData.accentColor));
@ -1635,3 +1675,167 @@ class _FifoCache<K, V> {
return _cache[key] = loader();
}
}
/// Defines the visual density of user interface components.
///
/// Density, in the context of a UI, is the vertical and horizontal
/// "compactness" of the components in the UI. It is unitless, since it means
/// different things to different UI components.
///
/// The default for visual densities is zero for both vertical and horizontal
/// densities, which corresponds to the default visual density of components in
/// the Material Design specification. It does not affect text sizes, icon
/// sizes, or padding values.
///
/// For example, for buttons, it affects the spacing around the child of the
/// button. For lists, it affects the distance between baselines of entries in
/// the list. For chips, it only affects the vertical size, not the horizontal
/// size.
///
/// See also:
///
/// * [ThemeData.visualDensity], where this property is used to specify the base
/// horizontal density of Material components.
/// * [Material design guidance on density](https://material.io/design/layout/applying-density.html).
class VisualDensity extends Diagnosticable {
/// A const constructor for [VisualDensity].
///
/// All of the arguments must be non-null, and [horizontal] and [vertical]
/// must be in the interval between [minimumDensity] and [maximumDensity],
/// inclusive.
const VisualDensity({
this.horizontal = 0.0,
this.vertical = 0.0,
}) : assert(horizontal != null),
assert(vertical != null),
assert(vertical <= maximumDensity),
assert(vertical >= minimumDensity),
assert(horizontal <= maximumDensity),
assert(horizontal >= minimumDensity);
/// The minimum allowed density.
static const double minimumDensity = -4.0;
/// The maximum allowed density.
static const double maximumDensity = 4.0;
/// The default profile for [VisualDensity] in [ThemeData].
///
/// This default value represents a visual density that is less dense than
/// either [comfortable] or [compact], and corresponds to density values of
/// zero in both axes.
static const VisualDensity standard = VisualDensity();
/// The profile for a "comfortable" interpretation of [VisualDensity].
///
/// Individual components will interpret the density value independently,
/// making themselves more visually dense than [standard] and less dense than
/// [compact] to different degrees based on the Material Design specification
/// of the "comfortable" setting for their particular use case.
///
/// It corresponds to a density value of -1 in both axes.
static const VisualDensity comfortable = VisualDensity(horizontal: -1.0, vertical: -1.0);
/// The profile for a "compact" interpretation of [VisualDensity].
///
/// Individual components will interpret the density value independently,
/// making themselves more visually dense than [standard] and [comfortable] to
/// different degrees based on the Material Design specification of the
/// "comfortable" setting for their particular use case.
///
/// It corresponds to a density value of -2 in both axes.
static const VisualDensity compact = VisualDensity(horizontal: -2.0, vertical: -2.0);
/// Copy the current [VisualDensity] with the given values replacing the
/// current values.
VisualDensity copyWith({
double horizontal,
double vertical,
}) {
return VisualDensity(
horizontal: horizontal ?? this.horizontal,
vertical: vertical ?? this.vertical,
);
}
/// The horizontal visual density of UI components.
///
/// This property affects only the horizontal spacing between and within
/// components, to allow for different UI visual densities. It does not affect
/// text sizes, icon sizes, or padding values. The default value is 0.0,
/// corresponding to the metrics specified in the Material Design
/// specification. The value can range from [minimumDensity] to
/// [maximumDensity], inclusive.
///
/// See also:
///
/// * [ThemeData.visualDensity], where this property is used to specify the base
/// horizontal density of Material components.
/// * [Material design guidance on density](https://material.io/design/layout/applying-density.html).
final double horizontal;
/// The vertical visual density of UI components.
///
/// This property affects only the vertical spacing between and within
/// components, to allow for different UI visual densities. It does not affect
/// text sizes, icon sizes, or padding values. The default value is 0.0,
/// corresponding to the metrics specified in the Material Design
/// specification. The value can range from [minimumDensity] to
/// [maximumDensity], inclusive.
///
/// See also:
///
/// * [ThemeData.visualDensity], where this property is used to specify the base
/// vertical density of Material components.
/// * [Material design guidance on density](https://material.io/design/layout/applying-density.html).
final double vertical;
/// The base adjustment in logical pixels of the visual density of UI components.
///
/// The input density values are multiplied by a constant to arrive at a base
/// size adjustment that fits material design guidelines.
///
/// Individual components may adjust this value based upon their own
/// individual interpretation of density.
Offset get baseSizeAdjustment {
// The number of logical pixels represented by an increase or decrease in
// density by one. The Material Design guidelines say to increment/decrement
// sized in terms of four pixel increments.
const double interval = 4.0;
return Offset(horizontal, vertical) * interval;
}
/// Linearly interpolate between two densities.
static VisualDensity lerp(VisualDensity a, VisualDensity b, double t) {
return VisualDensity(
horizontal: lerpDouble(a.horizontal, b.horizontal, t),
vertical: lerpDouble(a.horizontal, b.horizontal, t),
);
}
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType) {
return false;
}
final VisualDensity typedOther = other;
return horizontal == typedOther.horizontal
&& vertical == typedOther.vertical;
}
@override
int get hashCode => hashValues(horizontal, vertical);
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DoubleProperty('horizontal', horizontal, defaultValue: 0.0));
properties.add(DoubleProperty('vertical', vertical, defaultValue: 0.0));
}
@override
String toStringShort() {
return '${super.toStringShort()}(h: ${debugFormatDouble(horizontal)}, v: ${debugFormatDouble(vertical)})';
}
}

View file

@ -34,6 +34,18 @@ abstract class EdgeInsetsGeometry {
double get _start;
double get _top;
/// An [EdgeInsetsGeometry] with infinite offsets in each direction.
///
/// Can be used as an infinite upper bound for [clamp].
static const EdgeInsetsGeometry infinity = _MixedEdgeInsets.fromLRSETB(
double.infinity,
double.infinity,
double.infinity,
double.infinity,
double.infinity,
double.infinity,
);
/// Whether every dimension is non-negative.
bool get isNonNegative {
return _left >= 0.0
@ -146,6 +158,19 @@ abstract class EdgeInsetsGeometry {
);
}
/// Returns the a new [EdgeInsetsGeometry] object with all values greater than
/// or equal to `min`, and less than or equal to `max`.
EdgeInsetsGeometry clamp(EdgeInsetsGeometry min, EdgeInsetsGeometry max) {
return _MixedEdgeInsets.fromLRSETB(
_left.clamp(min._left, max._left),
_right.clamp(min._right, max._right),
_start.clamp(min._start, max._start),
_end.clamp(min._end, max._end),
_top.clamp(min._top, max._top),
_bottom.clamp(min._bottom, max._bottom),
);
}
/// Returns the [EdgeInsetsGeometry] object with each dimension negated.
///
/// This is the same as multiplying the object by -1.0.

View file

@ -690,6 +690,53 @@ void main() {
await tester.longPress(flatButton);
expect(didLongPressButton, isTrue);
});
testWidgets('FlatButton responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async {
return await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: FlatButton(
visualDensity: visualDensity,
key: key,
onPressed: () {},
child: useText ? const Text('Text') : Container(width: 100, height: 100, color: const Color(0xffff0000)),
),
),
),
),
);
}
await buildTest(const VisualDensity());
final RenderBox box = tester.renderObject(find.byKey(key));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(132, 100)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(156, 124)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(108, 100)));
await buildTest(const VisualDensity(), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(88, 48)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(112, 60)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(76, 36)));
});
}
TextStyle _iconStyle(WidgetTester tester, IconData icon) {

View file

@ -728,4 +728,51 @@ void main() {
);
expect(tester.widget<Material>(rawButtonMaterial).shape, const StadiumBorder());
});
testWidgets('MaterialButton responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async {
return await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: MaterialButton(
visualDensity: visualDensity,
key: key,
onPressed: () {},
child: useText ? const Text('Text') : Container(width: 100, height: 100, color: const Color(0xffff0000)),
),
),
),
),
);
}
await buildTest(const VisualDensity());
final RenderBox box = tester.renderObject(find.byKey(key));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(132, 100)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(156, 124)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(108, 100)));
await buildTest(const VisualDensity(), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(88, 48)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(112, 60)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(76, 36)));
});
}

View file

@ -978,6 +978,53 @@ void main() {
await tester.longPress(outlineButton);
expect(didLongPressButton, isTrue);
});
testWidgets('OutlineButton responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async {
return await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: OutlineButton(
visualDensity: visualDensity,
key: key,
onPressed: () {},
child: useText ? const Text('Text') : Container(width: 100, height: 100, color: const Color(0xffff0000)),
),
),
),
),
);
}
await buildTest(const VisualDensity());
final RenderBox box = tester.renderObject(find.byKey(key));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(132, 100)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(156, 124)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(108, 100)));
await buildTest(const VisualDensity(), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(88, 48)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(112, 60)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(76, 36)));
});
}
PhysicalModelLayer _findPhysicalLayer(Element element) {

View file

@ -557,6 +557,53 @@ void main() {
paintsExactlyCountTimes(#clipPath, 0),
);
});
testWidgets('RaisedButton responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async {
return await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: RaisedButton(
visualDensity: visualDensity,
key: key,
onPressed: () {},
child: useText ? const Text('Text') : Container(width: 100, height: 100, color: const Color(0xffff0000)),
),
),
),
),
);
}
await buildTest(const VisualDensity());
final RenderBox box = tester.renderObject(find.byKey(key));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(132, 100)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(156, 124)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(108, 100)));
await buildTest(const VisualDensity(), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(88, 48)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(112, 60)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(76, 36)));
});
}
TextStyle _iconStyle(WidgetTester tester, IconData icon) {

View file

@ -286,7 +286,7 @@ void main() {
expect(find.byKey(key).hitTestable(), findsOneWidget);
});
testWidgets('$RawMaterialButton can be expanded by parent constraints', (WidgetTester tester) async {
testWidgets('RawMaterialButton can be expanded by parent constraints', (WidgetTester tester) async {
const Key key = Key('test');
await tester.pumpWidget(
MaterialApp(
@ -306,7 +306,7 @@ void main() {
expect(tester.getSize(find.byKey(key)), const Size(800.0, 48.0));
});
testWidgets('$RawMaterialButton handles focus', (WidgetTester tester) async {
testWidgets('RawMaterialButton handles focus', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Button Focus');
const Key key = Key('test');
const Color focusColor = Color(0xff00ff00);
@ -334,7 +334,7 @@ void main() {
expect(box, paints..rect(color: focusColor));
});
testWidgets('$RawMaterialButton loses focus when disabled.', (WidgetTester tester) async {
testWidgets('RawMaterialButton loses focus when disabled.', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'RawMaterialButton');
await tester.pumpWidget(
MaterialApp(
@ -368,7 +368,7 @@ void main() {
expect(focusNode.hasPrimaryFocus, isFalse);
});
testWidgets("Disabled $RawMaterialButton can't be traversed to when disabled.", (WidgetTester tester) async {
testWidgets("Disabled RawMaterialButton can't be traversed to when disabled.", (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode(debugLabel: '$RawMaterialButton 1');
final FocusNode focusNode2 = FocusNode(debugLabel: '$RawMaterialButton 2');
@ -406,7 +406,7 @@ void main() {
expect(focusNode2.hasPrimaryFocus, isFalse);
});
testWidgets('$RawMaterialButton handles hover', (WidgetTester tester) async {
testWidgets('RawMaterialButton handles hover', (WidgetTester tester) async {
const Key key = Key('test');
const Color hoverColor = Color(0xff00ff00);
@ -510,4 +510,51 @@ void main() {
await tester.longPress(rawMaterialButton);
expect(didLongPressButton, isTrue);
});
testWidgets('RawMaterialButton responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async {
return await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Center(
child: RawMaterialButton(
visualDensity: visualDensity,
key: key,
onPressed: () {},
child: useText ? const Text('Text') : Container(width: 100, height: 100, color: const Color(0xffff0000)),
),
),
),
),
);
}
await buildTest(const VisualDensity());
final RenderBox box = tester.renderObject(find.byKey(key));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(100, 100)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(124, 124)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(100, 100)));
await buildTest(const VisualDensity(), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(88, 48)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(100, 60)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0), useText: true);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(76, 36)));
});
}

View file

@ -201,6 +201,7 @@ void main() {
final ThemeData theme = ThemeData.raw(
brightness: Brightness.dark,
visualDensity: const VisualDensity(),
primaryColor: Colors.black,
primaryColorBrightness: Brightness.dark,
primaryColorLight: Colors.black,
@ -279,6 +280,7 @@ void main() {
final ThemeData otherTheme = ThemeData.raw(
brightness: Brightness.light,
visualDensity: const VisualDensity(),
primaryColor: Colors.white,
primaryColorBrightness: Brightness.light,
primaryColorLight: Colors.white,