mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
878 lines
30 KiB
Dart
878 lines
30 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||
// Use of this source code is governed by a BSD-style license that can be
|
||
// found in the LICENSE file.
|
||
|
||
import 'dart:ui' show lerpDouble;
|
||
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter/widgets.dart';
|
||
|
||
import 'button_style.dart';
|
||
import 'button_style_button.dart';
|
||
import 'color_scheme.dart';
|
||
import 'colors.dart';
|
||
import 'constants.dart';
|
||
import 'filled_button_theme.dart';
|
||
import 'ink_well.dart';
|
||
import 'material_state.dart';
|
||
import 'theme.dart';
|
||
import 'theme_data.dart';
|
||
|
||
enum _FilledButtonVariant { filled, tonal }
|
||
|
||
/// A Material Design filled button.
|
||
///
|
||
/// Filled buttons have the most visual impact after the [FloatingActionButton],
|
||
/// and should be used for important, final actions that complete a flow,
|
||
/// like **Save**, **Join now**, or **Confirm**.
|
||
///
|
||
/// A filled button is a label [child] displayed on a [Material]
|
||
/// widget. The label's [Text] and [Icon] widgets are displayed in
|
||
/// [style]'s [ButtonStyle.foregroundColor] and the button's filled
|
||
/// background is the [ButtonStyle.backgroundColor].
|
||
///
|
||
/// The filled button's default style is defined by
|
||
/// [defaultStyleOf]. The style of this filled button can be
|
||
/// overridden with its [style] parameter. The style of all filled
|
||
/// buttons in a subtree can be overridden with the
|
||
/// [FilledButtonTheme], and the style of all of the filled
|
||
/// buttons in an app can be overridden with the [Theme]'s
|
||
/// [ThemeData.filledButtonTheme] property.
|
||
///
|
||
/// The static [styleFrom] method is a convenient way to create a
|
||
/// filled button [ButtonStyle] from simple values.
|
||
///
|
||
/// If [onPressed] and [onLongPress] callbacks are null, then the
|
||
/// button will be disabled.
|
||
///
|
||
/// To create a 'filled tonal' button, use [FilledButton.tonal].
|
||
///
|
||
/// {@tool dartpad}
|
||
/// This sample produces enabled and disabled filled and filled tonal
|
||
/// buttons.
|
||
///
|
||
/// ** See code in examples/api/lib/material/filled_button/filled_button.0.dart **
|
||
/// {@end-tool}
|
||
///
|
||
/// See also:
|
||
///
|
||
/// * [ElevatedButton], a filled button whose material elevates when pressed.
|
||
/// * [OutlinedButton], a button with an outlined border and no fill color.
|
||
/// * [TextButton], a button with no outline or fill color.
|
||
/// * <https://material.io/design/components/buttons.html>
|
||
/// * <https://m3.material.io/components/buttons>
|
||
class FilledButton extends ButtonStyleButton {
|
||
/// Create a FilledButton.
|
||
const FilledButton({
|
||
super.key,
|
||
required super.onPressed,
|
||
super.onLongPress,
|
||
super.onHover,
|
||
super.onFocusChange,
|
||
super.style,
|
||
super.focusNode,
|
||
super.autofocus = false,
|
||
super.clipBehavior = Clip.none,
|
||
super.statesController,
|
||
required super.child,
|
||
super.iconAlignment,
|
||
}) : _variant = _FilledButtonVariant.filled;
|
||
|
||
/// Create a filled button from [icon] and [label].
|
||
///
|
||
/// The icon and label are arranged in a row with padding at the start and end
|
||
/// and a gap between them.
|
||
///
|
||
/// If [icon] is null, will create a [FilledButton] instead.
|
||
///
|
||
/// {@macro flutter.material.ButtonStyleButton.iconAlignment}
|
||
///
|
||
factory FilledButton.icon({
|
||
Key? key,
|
||
required VoidCallback? onPressed,
|
||
VoidCallback? onLongPress,
|
||
ValueChanged<bool>? onHover,
|
||
ValueChanged<bool>? onFocusChange,
|
||
ButtonStyle? style,
|
||
FocusNode? focusNode,
|
||
bool? autofocus,
|
||
Clip? clipBehavior,
|
||
MaterialStatesController? statesController,
|
||
Widget? icon,
|
||
required Widget label,
|
||
IconAlignment iconAlignment = IconAlignment.start,
|
||
}) {
|
||
if (icon == null) {
|
||
return FilledButton(
|
||
key: key,
|
||
onPressed: onPressed,
|
||
onLongPress: onLongPress,
|
||
onHover: onHover,
|
||
onFocusChange: onFocusChange,
|
||
style: style,
|
||
focusNode: focusNode,
|
||
autofocus: autofocus ?? false,
|
||
clipBehavior: clipBehavior ?? Clip.none,
|
||
statesController: statesController,
|
||
child: label,
|
||
);
|
||
}
|
||
return _FilledButtonWithIcon(
|
||
key: key,
|
||
onPressed: onPressed,
|
||
onLongPress: onLongPress,
|
||
onHover: onHover,
|
||
onFocusChange: onFocusChange,
|
||
style: style,
|
||
focusNode: focusNode,
|
||
autofocus: autofocus ?? false,
|
||
clipBehavior: clipBehavior ?? Clip.none,
|
||
statesController: statesController,
|
||
icon: icon,
|
||
label: label,
|
||
iconAlignment: iconAlignment,
|
||
);
|
||
}
|
||
|
||
/// Create a tonal variant of FilledButton.
|
||
///
|
||
/// A filled tonal button is an alternative middle ground between
|
||
/// [FilledButton] and [OutlinedButton]. They’re useful in contexts where
|
||
/// a lower-priority button requires slightly more emphasis than an
|
||
/// outline would give, such as "Next" in an onboarding flow.
|
||
const FilledButton.tonal({
|
||
super.key,
|
||
required super.onPressed,
|
||
super.onLongPress,
|
||
super.onHover,
|
||
super.onFocusChange,
|
||
super.style,
|
||
super.focusNode,
|
||
super.autofocus = false,
|
||
super.clipBehavior = Clip.none,
|
||
super.statesController,
|
||
required super.child,
|
||
}) : _variant = _FilledButtonVariant.tonal;
|
||
|
||
/// Create a filled tonal button from [icon] and [label].
|
||
///
|
||
/// The [icon] and [label] are arranged in a row with padding at the start and
|
||
/// end and a gap between them.
|
||
///
|
||
/// If [icon] is null, will create a [FilledButton.tonal] instead.
|
||
factory FilledButton.tonalIcon({
|
||
Key? key,
|
||
required VoidCallback? onPressed,
|
||
VoidCallback? onLongPress,
|
||
ValueChanged<bool>? onHover,
|
||
ValueChanged<bool>? onFocusChange,
|
||
ButtonStyle? style,
|
||
FocusNode? focusNode,
|
||
bool? autofocus,
|
||
Clip? clipBehavior,
|
||
MaterialStatesController? statesController,
|
||
Widget? icon,
|
||
required Widget label,
|
||
IconAlignment iconAlignment = IconAlignment.start,
|
||
}) {
|
||
if (icon == null) {
|
||
return FilledButton.tonal(
|
||
key: key,
|
||
onPressed: onPressed,
|
||
onLongPress: onLongPress,
|
||
onHover: onHover,
|
||
onFocusChange: onFocusChange,
|
||
style: style,
|
||
focusNode: focusNode,
|
||
autofocus: autofocus ?? false,
|
||
clipBehavior: clipBehavior ?? Clip.none,
|
||
statesController: statesController,
|
||
child: label,
|
||
);
|
||
}
|
||
return _FilledButtonWithIcon.tonal(
|
||
key: key,
|
||
onPressed: onPressed,
|
||
onLongPress: onLongPress,
|
||
onHover: onHover,
|
||
onFocusChange: onFocusChange,
|
||
style: style,
|
||
focusNode: focusNode,
|
||
autofocus: autofocus,
|
||
clipBehavior: clipBehavior,
|
||
statesController: statesController,
|
||
icon: icon,
|
||
label: label,
|
||
iconAlignment: iconAlignment,
|
||
);
|
||
}
|
||
|
||
/// A static convenience method that constructs a filled button
|
||
/// [ButtonStyle] given simple values.
|
||
///
|
||
/// The [foregroundColor] and [disabledForegroundColor] colors are used
|
||
/// to create a [MaterialStateProperty] [ButtonStyle.foregroundColor], and
|
||
/// a derived [ButtonStyle.overlayColor] if [overlayColor] isn't specified.
|
||
///
|
||
/// If [overlayColor] is specified and its value is [Colors.transparent]
|
||
/// then the pressed/focused/hovered highlights are effectively defeated.
|
||
/// Otherwise a [MaterialStateProperty] with the same opacities as the
|
||
/// default is created.
|
||
///
|
||
/// Similarly, the [enabledMouseCursor] and [disabledMouseCursor]
|
||
/// parameters are used to construct [ButtonStyle.mouseCursor].
|
||
///
|
||
/// The button's elevations are defined relative to the [elevation]
|
||
/// parameter. The disabled elevation is the same as the parameter
|
||
/// value, [elevation] + 2 is used when the button is hovered
|
||
/// or focused, and elevation + 6 is used when the button is pressed.
|
||
///
|
||
/// All of the other parameters are either used directly or used to
|
||
/// create a [MaterialStateProperty] with a single value for all
|
||
/// states.
|
||
///
|
||
/// All parameters default to null, by default this method returns
|
||
/// a [ButtonStyle] that doesn't override anything.
|
||
///
|
||
/// For example, to override the default text and icon colors for a
|
||
/// [FilledButton], as well as its overlay color, with all of the
|
||
/// standard opacity adjustments for the pressed, focused, and
|
||
/// hovered states, one could write:
|
||
///
|
||
/// ```dart
|
||
/// FilledButton(
|
||
/// style: FilledButton.styleFrom(foregroundColor: Colors.green),
|
||
/// onPressed: () {},
|
||
/// child: const Text('Filled button'),
|
||
/// );
|
||
/// ```
|
||
///
|
||
/// or for a Filled tonal variant:
|
||
/// ```dart
|
||
/// FilledButton.tonal(
|
||
/// style: FilledButton.styleFrom(foregroundColor: Colors.green),
|
||
/// onPressed: () {},
|
||
/// child: const Text('Filled tonal button'),
|
||
/// );
|
||
/// ```
|
||
static ButtonStyle styleFrom({
|
||
Color? foregroundColor,
|
||
Color? backgroundColor,
|
||
Color? disabledForegroundColor,
|
||
Color? disabledBackgroundColor,
|
||
Color? shadowColor,
|
||
Color? surfaceTintColor,
|
||
Color? iconColor,
|
||
Color? disabledIconColor,
|
||
Color? overlayColor,
|
||
double? elevation,
|
||
TextStyle? textStyle,
|
||
EdgeInsetsGeometry? padding,
|
||
Size? minimumSize,
|
||
Size? fixedSize,
|
||
Size? maximumSize,
|
||
BorderSide? side,
|
||
OutlinedBorder? shape,
|
||
MouseCursor? enabledMouseCursor,
|
||
MouseCursor? disabledMouseCursor,
|
||
VisualDensity? visualDensity,
|
||
MaterialTapTargetSize? tapTargetSize,
|
||
Duration? animationDuration,
|
||
bool? enableFeedback,
|
||
AlignmentGeometry? alignment,
|
||
InteractiveInkFeatureFactory? splashFactory,
|
||
ButtonLayerBuilder? backgroundBuilder,
|
||
ButtonLayerBuilder? foregroundBuilder,
|
||
}) {
|
||
final MaterialStateProperty<Color?>? foregroundColorProp = switch ((foregroundColor, disabledForegroundColor)) {
|
||
(null, null) => null,
|
||
(_, _) => _FilledButtonDefaultColor(foregroundColor, disabledForegroundColor),
|
||
};
|
||
final MaterialStateProperty<Color?>? backgroundColorProp = switch ((backgroundColor, disabledBackgroundColor)) {
|
||
(null, null) => null,
|
||
(_, _) => _FilledButtonDefaultColor(backgroundColor, disabledBackgroundColor),
|
||
};
|
||
final MaterialStateProperty<Color?>? iconColorProp = switch ((iconColor, disabledIconColor)) {
|
||
(null, null) => null,
|
||
(_, _) => _FilledButtonDefaultColor(iconColor, disabledIconColor),
|
||
};
|
||
final MaterialStateProperty<Color?>? overlayColorProp = switch ((foregroundColor, overlayColor)) {
|
||
(null, null) => null,
|
||
(_, final Color overlayColor) when overlayColor.value == 0 => const MaterialStatePropertyAll<Color?>(Colors.transparent),
|
||
(_, _) => _FilledButtonDefaultOverlay((overlayColor ?? foregroundColor)!),
|
||
};
|
||
final MaterialStateProperty<MouseCursor?> mouseCursor = _FilledButtonDefaultMouseCursor(enabledMouseCursor, disabledMouseCursor);
|
||
|
||
return ButtonStyle(
|
||
textStyle: MaterialStatePropertyAll<TextStyle?>(textStyle),
|
||
backgroundColor: backgroundColorProp,
|
||
foregroundColor: foregroundColorProp,
|
||
overlayColor: overlayColorProp,
|
||
shadowColor: ButtonStyleButton.allOrNull<Color>(shadowColor),
|
||
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
|
||
iconColor: iconColorProp,
|
||
elevation: ButtonStyleButton.allOrNull(elevation),
|
||
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
|
||
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
|
||
fixedSize: ButtonStyleButton.allOrNull<Size>(fixedSize),
|
||
maximumSize: ButtonStyleButton.allOrNull<Size>(maximumSize),
|
||
side: ButtonStyleButton.allOrNull<BorderSide>(side),
|
||
shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
|
||
mouseCursor: mouseCursor,
|
||
visualDensity: visualDensity,
|
||
tapTargetSize: tapTargetSize,
|
||
animationDuration: animationDuration,
|
||
enableFeedback: enableFeedback,
|
||
alignment: alignment,
|
||
splashFactory: splashFactory,
|
||
backgroundBuilder: backgroundBuilder,
|
||
foregroundBuilder: foregroundBuilder,
|
||
);
|
||
}
|
||
|
||
final _FilledButtonVariant _variant;
|
||
|
||
/// Defines the button's default appearance.
|
||
///
|
||
/// The button [child]'s [Text] and [Icon] widgets are rendered with
|
||
/// the [ButtonStyle]'s foreground color. The button's [InkWell] adds
|
||
/// the style's overlay color when the button is focused, hovered
|
||
/// or pressed. The button's background color becomes its [Material]
|
||
/// color.
|
||
///
|
||
/// All of the ButtonStyle's defaults appear below. In this list
|
||
/// "Theme.foo" is shorthand for `Theme.of(context).foo`. Color
|
||
/// scheme values like "onSurface(0.38)" are shorthand for
|
||
/// `onSurface.withOpacity(0.38)`. [MaterialStateProperty] valued
|
||
/// properties that are not followed by a sublist have the same
|
||
/// value for all states, otherwise the values are as specified for
|
||
/// each state, and "others" means all other states.
|
||
///
|
||
/// {@macro flutter.material.elevated_button.default_font_size}
|
||
///
|
||
/// The color of the [ButtonStyle.textStyle] is not used, the
|
||
/// [ButtonStyle.foregroundColor] color is used instead.
|
||
///
|
||
/// * `textStyle` - Theme.textTheme.labelLarge
|
||
/// * `backgroundColor`
|
||
/// * disabled - Theme.colorScheme.onSurface(0.12)
|
||
/// * others - Theme.colorScheme.secondaryContainer
|
||
/// * `foregroundColor`
|
||
/// * disabled - Theme.colorScheme.onSurface(0.38)
|
||
/// * others - Theme.colorScheme.onSecondaryContainer
|
||
/// * `overlayColor`
|
||
/// * hovered - Theme.colorScheme.onSecondaryContainer(0.08)
|
||
/// * focused or pressed - Theme.colorScheme.onSecondaryContainer(0.12)
|
||
/// * `shadowColor` - Theme.colorScheme.shadow
|
||
/// * `surfaceTintColor` - null
|
||
/// * `elevation`
|
||
/// * disabled - 0
|
||
/// * default - 0
|
||
/// * hovered - 1
|
||
/// * focused or pressed - 0
|
||
/// * `padding`
|
||
/// * `default font size <= 14` - horizontal(16)
|
||
/// * `14 < default font size <= 28` - lerp(horizontal(16), horizontal(8))
|
||
/// * `28 < default font size <= 36` - lerp(horizontal(8), horizontal(4))
|
||
/// * `36 < default font size` - horizontal(4)
|
||
/// * `minimumSize` - Size(64, 40)
|
||
/// * `fixedSize` - null
|
||
/// * `maximumSize` - Size.infinite
|
||
/// * `side` - null
|
||
/// * `shape` - StadiumBorder()
|
||
/// * `mouseCursor`
|
||
/// * disabled - SystemMouseCursors.basic
|
||
/// * others - SystemMouseCursors.click
|
||
/// * `visualDensity` - Theme.visualDensity
|
||
/// * `tapTargetSize` - Theme.materialTapTargetSize
|
||
/// * `animationDuration` - kThemeChangeDuration
|
||
/// * `enableFeedback` - true
|
||
/// * `alignment` - Alignment.center
|
||
/// * `splashFactory` - Theme.splashFactory
|
||
///
|
||
/// The default padding values for the [FilledButton.icon] factory are slightly different:
|
||
///
|
||
/// * `padding`
|
||
/// * `default font size <= 14` - start(12) end(16)
|
||
/// * `14 < default font size <= 28` - lerp(start(12) end(16), horizontal(8))
|
||
/// * `28 < default font size <= 36` - lerp(horizontal(8), horizontal(4))
|
||
/// * `36 < default font size` - horizontal(4)
|
||
///
|
||
/// The default value for `side`, which defines the appearance of the button's
|
||
/// outline, is null. That means that the outline is defined by the button
|
||
/// shape's [OutlinedBorder.side]. Typically the default value of an
|
||
/// [OutlinedBorder]'s side is [BorderSide.none], so an outline is not drawn.
|
||
///
|
||
/// ## Material 3 defaults
|
||
///
|
||
/// If [ThemeData.useMaterial3] is set to true the following defaults will
|
||
/// be used:
|
||
///
|
||
/// * `textStyle` - Theme.textTheme.labelLarge
|
||
/// * `backgroundColor`
|
||
/// * disabled - Theme.colorScheme.onSurface(0.12)
|
||
/// * others - Theme.colorScheme.secondaryContainer
|
||
/// * `foregroundColor`
|
||
/// * disabled - Theme.colorScheme.onSurface(0.38)
|
||
/// * others - Theme.colorScheme.onSecondaryContainer
|
||
/// * `overlayColor`
|
||
/// * hovered - Theme.colorScheme.onSecondaryContainer(0.08)
|
||
/// * focused or pressed - Theme.colorScheme.onSecondaryContainer(0.1)
|
||
/// * `shadowColor` - Theme.colorScheme.shadow
|
||
/// * `surfaceTintColor` - Colors.transparent
|
||
/// * `elevation`
|
||
/// * disabled - 0
|
||
/// * default - 1
|
||
/// * hovered - 3
|
||
/// * focused or pressed - 1
|
||
/// * `padding`
|
||
/// * `default font size <= 14` - horizontal(24)
|
||
/// * `14 < default font size <= 28` - lerp(horizontal(24), horizontal(12))
|
||
/// * `28 < default font size <= 36` - lerp(horizontal(12), horizontal(6))
|
||
/// * `36 < default font size` - horizontal(6)
|
||
/// * `minimumSize` - Size(64, 40)
|
||
/// * `fixedSize` - null
|
||
/// * `maximumSize` - Size.infinite
|
||
/// * `side` - null
|
||
/// * `shape` - StadiumBorder()
|
||
/// * `mouseCursor`
|
||
/// * disabled - SystemMouseCursors.basic
|
||
/// * others - SystemMouseCursors.click
|
||
/// * `visualDensity` - Theme.visualDensity
|
||
/// * `tapTargetSize` - Theme.materialTapTargetSize
|
||
/// * `animationDuration` - kThemeChangeDuration
|
||
/// * `enableFeedback` - true
|
||
/// * `alignment` - Alignment.center
|
||
/// * `splashFactory` - Theme.splashFactory
|
||
///
|
||
/// For the [FilledButton.icon] factory, the start (generally the left) value of
|
||
/// [padding] is reduced from 24 to 16.
|
||
@override
|
||
ButtonStyle defaultStyleOf(BuildContext context) {
|
||
return switch (_variant) {
|
||
_FilledButtonVariant.filled => _FilledButtonDefaultsM3(context),
|
||
_FilledButtonVariant.tonal => _FilledTonalButtonDefaultsM3(context),
|
||
};
|
||
}
|
||
|
||
/// Returns the [FilledButtonThemeData.style] of the closest
|
||
/// [FilledButtonTheme] ancestor.
|
||
@override
|
||
ButtonStyle? themeStyleOf(BuildContext context) {
|
||
return FilledButtonTheme.of(context).style;
|
||
}
|
||
}
|
||
|
||
EdgeInsetsGeometry _scaledPadding(BuildContext context) {
|
||
final ThemeData theme = Theme.of(context);
|
||
final double defaultFontSize = theme.textTheme.labelLarge?.fontSize ?? 14.0;
|
||
final double effectiveTextScale = MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0;
|
||
final double padding1x = theme.useMaterial3 ? 24.0 : 16.0;
|
||
return ButtonStyleButton.scaledPadding(
|
||
EdgeInsets.symmetric(horizontal: padding1x),
|
||
EdgeInsets.symmetric(horizontal: padding1x / 2),
|
||
EdgeInsets.symmetric(horizontal: padding1x / 2 / 2),
|
||
effectiveTextScale,
|
||
);
|
||
}
|
||
|
||
@immutable
|
||
class _FilledButtonDefaultColor extends MaterialStateProperty<Color?> with Diagnosticable {
|
||
_FilledButtonDefaultColor(this.color, this.disabled);
|
||
|
||
final Color? color;
|
||
final Color? disabled;
|
||
|
||
@override
|
||
Color? resolve(Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return disabled;
|
||
}
|
||
return color;
|
||
}
|
||
}
|
||
|
||
@immutable
|
||
class _FilledButtonDefaultOverlay extends MaterialStateProperty<Color?> with Diagnosticable {
|
||
_FilledButtonDefaultOverlay(this.overlay);
|
||
|
||
final Color overlay;
|
||
|
||
@override
|
||
Color? resolve(Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.pressed)) {
|
||
return overlay.withOpacity(0.1);
|
||
}
|
||
if (states.contains(MaterialState.hovered)) {
|
||
return overlay.withOpacity(0.08);
|
||
}
|
||
if (states.contains(MaterialState.focused)) {
|
||
return overlay.withOpacity(0.1);
|
||
}
|
||
return null;
|
||
}
|
||
}
|
||
|
||
@immutable
|
||
class _FilledButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor?> with Diagnosticable {
|
||
_FilledButtonDefaultMouseCursor(this.enabledCursor, this.disabledCursor);
|
||
|
||
final MouseCursor? enabledCursor;
|
||
final MouseCursor? disabledCursor;
|
||
|
||
@override
|
||
MouseCursor? resolve(Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return disabledCursor;
|
||
}
|
||
return enabledCursor;
|
||
}
|
||
}
|
||
|
||
class _FilledButtonWithIcon extends FilledButton {
|
||
_FilledButtonWithIcon({
|
||
super.key,
|
||
required super.onPressed,
|
||
super.onLongPress,
|
||
super.onHover,
|
||
super.onFocusChange,
|
||
super.style,
|
||
super.focusNode,
|
||
bool? autofocus,
|
||
super.clipBehavior,
|
||
super.statesController,
|
||
required Widget icon,
|
||
required Widget label,
|
||
super.iconAlignment,
|
||
}) : super(
|
||
autofocus: autofocus ?? false,
|
||
child: _FilledButtonWithIconChild(
|
||
icon: icon,
|
||
label: label,
|
||
buttonStyle: style,
|
||
iconAlignment: iconAlignment,
|
||
),
|
||
);
|
||
|
||
_FilledButtonWithIcon.tonal({
|
||
super.key,
|
||
required super.onPressed,
|
||
super.onLongPress,
|
||
super.onHover,
|
||
super.onFocusChange,
|
||
super.style,
|
||
super.focusNode,
|
||
bool? autofocus,
|
||
super.clipBehavior,
|
||
super.statesController,
|
||
required Widget icon,
|
||
required Widget label,
|
||
required IconAlignment iconAlignment,
|
||
}) : super.tonal(
|
||
autofocus: autofocus ?? false,
|
||
child: _FilledButtonWithIconChild(
|
||
icon: icon,
|
||
label: label,
|
||
buttonStyle: style,
|
||
iconAlignment: iconAlignment,
|
||
),
|
||
);
|
||
|
||
@override
|
||
ButtonStyle defaultStyleOf(BuildContext context) {
|
||
final bool useMaterial3 = Theme.of(context).useMaterial3;
|
||
final ButtonStyle buttonStyle = super.defaultStyleOf(context);
|
||
final double defaultFontSize = buttonStyle.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
|
||
final double effectiveTextScale = MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0;
|
||
|
||
final EdgeInsetsGeometry scaledPadding = useMaterial3
|
||
? ButtonStyleButton.scaledPadding(
|
||
const EdgeInsetsDirectional.fromSTEB(16, 0, 24, 0),
|
||
const EdgeInsetsDirectional.fromSTEB(8, 0, 12, 0),
|
||
const EdgeInsetsDirectional.fromSTEB(4, 0, 6, 0),
|
||
effectiveTextScale,
|
||
) : ButtonStyleButton.scaledPadding(
|
||
const EdgeInsetsDirectional.fromSTEB(12, 0, 16, 0),
|
||
const EdgeInsets.symmetric(horizontal: 8),
|
||
const EdgeInsetsDirectional.fromSTEB(8, 0, 4, 0),
|
||
effectiveTextScale,
|
||
);
|
||
return buttonStyle.copyWith(
|
||
padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _FilledButtonWithIconChild extends StatelessWidget {
|
||
const _FilledButtonWithIconChild({
|
||
required this.label,
|
||
required this.icon,
|
||
required this.buttonStyle,
|
||
required this.iconAlignment,
|
||
});
|
||
|
||
final Widget label;
|
||
final Widget icon;
|
||
final ButtonStyle? buttonStyle;
|
||
final IconAlignment iconAlignment;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final double defaultFontSize = buttonStyle?.textStyle?.resolve(const <MaterialState>{})?.fontSize ?? 14.0;
|
||
final double scale = clampDouble(MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0, 1.0, 2.0) - 1.0;
|
||
// Adjust the gap based on the text scale factor. Start at 8, and lerp
|
||
// to 4 based on how large the text is.
|
||
final double gap = lerpDouble(8, 4, scale)!;
|
||
return Row(
|
||
mainAxisSize: MainAxisSize.min,
|
||
children: iconAlignment == IconAlignment.start
|
||
? <Widget>[icon, SizedBox(width: gap), Flexible(child: label)]
|
||
: <Widget>[Flexible(child: label), SizedBox(width: gap), icon],
|
||
);
|
||
}
|
||
}
|
||
|
||
// BEGIN GENERATED TOKEN PROPERTIES - FilledButton
|
||
|
||
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||
// "END GENERATED" comments are generated from data in the Material
|
||
// Design token database by the script:
|
||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||
|
||
class _FilledButtonDefaultsM3 extends ButtonStyle {
|
||
_FilledButtonDefaultsM3(this.context)
|
||
: super(
|
||
animationDuration: kThemeChangeDuration,
|
||
enableFeedback: true,
|
||
alignment: Alignment.center,
|
||
);
|
||
|
||
final BuildContext context;
|
||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||
|
||
@override
|
||
MaterialStateProperty<TextStyle?> get textStyle =>
|
||
MaterialStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge);
|
||
|
||
@override
|
||
MaterialStateProperty<Color?>? get backgroundColor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return _colors.onSurface.withOpacity(0.12);
|
||
}
|
||
return _colors.primary;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<Color?>? get foregroundColor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return _colors.onSurface.withOpacity(0.38);
|
||
}
|
||
return _colors.onPrimary;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<Color?>? get overlayColor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.pressed)) {
|
||
return _colors.onPrimary.withOpacity(0.1);
|
||
}
|
||
if (states.contains(MaterialState.hovered)) {
|
||
return _colors.onPrimary.withOpacity(0.08);
|
||
}
|
||
if (states.contains(MaterialState.focused)) {
|
||
return _colors.onPrimary.withOpacity(0.1);
|
||
}
|
||
return null;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<Color>? get shadowColor =>
|
||
MaterialStatePropertyAll<Color>(_colors.shadow);
|
||
|
||
@override
|
||
MaterialStateProperty<Color>? get surfaceTintColor =>
|
||
const MaterialStatePropertyAll<Color>(Colors.transparent);
|
||
|
||
@override
|
||
MaterialStateProperty<double>? get elevation =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return 0.0;
|
||
}
|
||
if (states.contains(MaterialState.pressed)) {
|
||
return 0.0;
|
||
}
|
||
if (states.contains(MaterialState.hovered)) {
|
||
return 1.0;
|
||
}
|
||
if (states.contains(MaterialState.focused)) {
|
||
return 0.0;
|
||
}
|
||
return 0.0;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
|
||
MaterialStatePropertyAll<EdgeInsetsGeometry>(_scaledPadding(context));
|
||
|
||
@override
|
||
MaterialStateProperty<Size>? get minimumSize =>
|
||
const MaterialStatePropertyAll<Size>(Size(64.0, 40.0));
|
||
|
||
// No default fixedSize
|
||
|
||
@override
|
||
MaterialStateProperty<Size>? get maximumSize =>
|
||
const MaterialStatePropertyAll<Size>(Size.infinite);
|
||
|
||
// No default side
|
||
|
||
@override
|
||
MaterialStateProperty<OutlinedBorder>? get shape =>
|
||
const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder());
|
||
|
||
@override
|
||
MaterialStateProperty<MouseCursor?>? get mouseCursor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return SystemMouseCursors.basic;
|
||
}
|
||
return SystemMouseCursors.click;
|
||
});
|
||
|
||
@override
|
||
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
|
||
|
||
@override
|
||
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
|
||
|
||
@override
|
||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||
}
|
||
|
||
// END GENERATED TOKEN PROPERTIES - FilledButton
|
||
|
||
// BEGIN GENERATED TOKEN PROPERTIES - FilledTonalButton
|
||
|
||
// Do not edit by hand. The code between the "BEGIN GENERATED" and
|
||
// "END GENERATED" comments are generated from data in the Material
|
||
// Design token database by the script:
|
||
// dev/tools/gen_defaults/bin/gen_defaults.dart.
|
||
|
||
class _FilledTonalButtonDefaultsM3 extends ButtonStyle {
|
||
_FilledTonalButtonDefaultsM3(this.context)
|
||
: super(
|
||
animationDuration: kThemeChangeDuration,
|
||
enableFeedback: true,
|
||
alignment: Alignment.center,
|
||
);
|
||
|
||
final BuildContext context;
|
||
late final ColorScheme _colors = Theme.of(context).colorScheme;
|
||
|
||
@override
|
||
MaterialStateProperty<TextStyle?> get textStyle =>
|
||
MaterialStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge);
|
||
|
||
@override
|
||
MaterialStateProperty<Color?>? get backgroundColor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return _colors.onSurface.withOpacity(0.12);
|
||
}
|
||
return _colors.secondaryContainer;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<Color?>? get foregroundColor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return _colors.onSurface.withOpacity(0.38);
|
||
}
|
||
return _colors.onSecondaryContainer;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<Color?>? get overlayColor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.pressed)) {
|
||
return _colors.onSecondaryContainer.withOpacity(0.1);
|
||
}
|
||
if (states.contains(MaterialState.hovered)) {
|
||
return _colors.onSecondaryContainer.withOpacity(0.08);
|
||
}
|
||
if (states.contains(MaterialState.focused)) {
|
||
return _colors.onSecondaryContainer.withOpacity(0.1);
|
||
}
|
||
return null;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<Color>? get shadowColor =>
|
||
MaterialStatePropertyAll<Color>(_colors.shadow);
|
||
|
||
@override
|
||
MaterialStateProperty<Color>? get surfaceTintColor =>
|
||
const MaterialStatePropertyAll<Color>(Colors.transparent);
|
||
|
||
@override
|
||
MaterialStateProperty<double>? get elevation =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return 0.0;
|
||
}
|
||
if (states.contains(MaterialState.pressed)) {
|
||
return 0.0;
|
||
}
|
||
if (states.contains(MaterialState.hovered)) {
|
||
return 1.0;
|
||
}
|
||
if (states.contains(MaterialState.focused)) {
|
||
return 0.0;
|
||
}
|
||
return 0.0;
|
||
});
|
||
|
||
@override
|
||
MaterialStateProperty<EdgeInsetsGeometry>? get padding =>
|
||
MaterialStatePropertyAll<EdgeInsetsGeometry>(_scaledPadding(context));
|
||
|
||
@override
|
||
MaterialStateProperty<Size>? get minimumSize =>
|
||
const MaterialStatePropertyAll<Size>(Size(64.0, 40.0));
|
||
|
||
// No default fixedSize
|
||
|
||
@override
|
||
MaterialStateProperty<Size>? get maximumSize =>
|
||
const MaterialStatePropertyAll<Size>(Size.infinite);
|
||
|
||
// No default side
|
||
|
||
@override
|
||
MaterialStateProperty<OutlinedBorder>? get shape =>
|
||
const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder());
|
||
|
||
@override
|
||
MaterialStateProperty<MouseCursor?>? get mouseCursor =>
|
||
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||
if (states.contains(MaterialState.disabled)) {
|
||
return SystemMouseCursors.basic;
|
||
}
|
||
return SystemMouseCursors.click;
|
||
});
|
||
|
||
@override
|
||
VisualDensity? get visualDensity => Theme.of(context).visualDensity;
|
||
|
||
@override
|
||
MaterialTapTargetSize? get tapTargetSize => Theme.of(context).materialTapTargetSize;
|
||
|
||
@override
|
||
InteractiveInkFeatureFactory? get splashFactory => Theme.of(context).splashFactory;
|
||
}
|
||
|
||
// END GENERATED TOKEN PROPERTIES - FilledTonalButton
|