mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
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:
parent
1ac17c14fc
commit
185da9b0af
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)})';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue