Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Added IconButtonTheme and apply it to IconButton in M3 #108332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dev/tools/gen_defaults/lib/icon_button_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ class _${blockName}DefaultsM3 extends ButtonStyle {
MaterialStateProperty<Size>? get maximumSize =>
ButtonStyleButton.allOrNull<Size>(Size.infinite);

@override
MaterialStateProperty<double>? get iconSize =>
ButtonStyleButton.allOrNull<double>(${tokens["md.comp.icon-button.icon.size"]});

// No default side

@override
Expand Down
1 change: 1 addition & 0 deletions packages/flutter/lib/material.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export 'src/material/flutter_logo.dart';
export 'src/material/grid_tile.dart';
export 'src/material/grid_tile_bar.dart';
export 'src/material/icon_button.dart';
export 'src/material/icon_button_theme.dart';
export 'src/material/icons.dart';
export 'src/material/ink_decoration.dart';
export 'src/material/ink_highlight.dart';
Expand Down
12 changes: 12 additions & 0 deletions packages/flutter/lib/src/material/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import 'package:flutter/painting.dart';

import 'colors.dart';

/// The minimum dimension of any interactive region according to Material
/// guidelines.
///
Expand Down Expand Up @@ -47,3 +49,13 @@ const EdgeInsets kTabLabelPadding = EdgeInsets.symmetric(horizontal: 16.0);

/// The padding added around material list items.
const EdgeInsets kMaterialListPadding = EdgeInsets.symmetric(vertical: 8.0);

/// The default color for [ThemeData.iconTheme] when [ThemeData.brightness] is
/// [Brightness.light]. This color is used in [IconButton] to detect whether
/// [IconTheme.of(context).color] is the same as the default color of [ThemeData.iconTheme].
const Color kDefaultIconLightColor = Colors.white;

/// The default color for [ThemeData.iconTheme] when [ThemeData.brightness] is
/// [Brightness.dark]. This color is used in [IconButton] to detect whether
/// [IconTheme.of(context).color] is the same as the default color of [ThemeData.iconTheme].
const Color kDefaultIconDarkColor = Colors.black87;
130 changes: 83 additions & 47 deletions packages/flutter/lib/src/material/icon_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'color_scheme.dart';
import 'colors.dart';
import 'constants.dart';
import 'debug.dart';
import 'icon_button_theme.dart';
import 'icons.dart';
import 'ink_well.dart';
import 'material.dart';
Expand All @@ -37,7 +38,9 @@ const double _kMinButtonSize = kMinInteractiveDimension;
/// If the [onPressed] callback is null, then the button will be disabled and
/// will not react to touch.
///
/// Requires one of its ancestors to be a [Material] widget.
/// Requires one of its ancestors to be a [Material] widget. In Material Design 3,
/// this requirement no longer exists because this widget builds a subclass of
/// [ButtonStyleButton].
///
/// The hit region of an icon button will, if possible, be at least
/// kMinInteractiveDimension pixels in size, regardless of the actual
Expand Down Expand Up @@ -109,6 +112,12 @@ const double _kMinButtonSize = kMinInteractiveDimension;
/// null then it will behave as a toggle button. If [isSelected] is true then it will
/// show [selectedIcon], if it false it will show the normal [icon].
///
/// In Material Design 3, both [IconTheme] and [IconButtonTheme] are used to override the default style
/// of [IconButton]. If both themes exist, the [IconButtonTheme] will override [IconTheme] no matter
/// which is closer to the [IconButton]. Each [IconButton]'s property is resolved by the order of
/// precedence: widget property, [IconButtonTheme] property, [IconTheme] property and
/// internal default property value.
///
/// {@tool dartpad}
/// This sample shows creation of [IconButton] widgets for standard, filled,
/// filled tonal and outlined types, as described in: https://m3.material.io/components/icon-buttons/overview
Expand Down Expand Up @@ -139,19 +148,19 @@ class IconButton extends StatelessWidget {
/// Icon buttons are commonly used in the [AppBar.actions] field, but they can
/// be used in many other places as well.
///
/// Requires one of its ancestors to be a [Material] widget.
/// Requires one of its ancestors to be a [Material] widget. This requirement
/// no longer exists if [ThemeData.useMaterial3] is set to true.
///
/// The [iconSize], [padding], [autofocus], and [alignment] arguments must not
/// be null (though they each have default values).
/// [autofocus] argument must not be null (though it has default value).
///
/// The [icon] argument must be specified, and is typically either an [Icon]
/// or an [ImageIcon].
const IconButton({
super.key,
this.iconSize,
this.visualDensity,
this.padding = const EdgeInsets.all(8.0),
this.alignment = Alignment.center,
this.padding,
this.alignment,
this.splashRadius,
this.color,
this.focusColor,
Expand All @@ -164,15 +173,13 @@ class IconButton extends StatelessWidget {
this.focusNode,
this.autofocus = false,
this.tooltip,
this.enableFeedback = true,
this.enableFeedback,
this.constraints,
this.style,
this.isSelected,
this.selectedIcon,
required this.icon,
}) : assert(padding != null),
assert(alignment != null),
assert(splashRadius == null || splashRadius > 0),
}) : assert(splashRadius == null || splashRadius > 0),
assert(autofocus != null),
assert(icon != null);

Expand All @@ -187,6 +194,10 @@ class IconButton extends StatelessWidget {
/// fit the [Icon]. If you were to set the size of the [Icon] using
/// [Icon.size] instead, then the [IconButton] would default to 24.0 and then
/// the [Icon] itself would likely get clipped.
///
/// If [ThemeData.useMaterial3] is set to true and this is null, the size of the
/// [IconButton] would default to 24.0. The size given here is passed down to the
/// [ButtonStyle.iconSize] property.
final double? iconSize;

/// Defines how compact the icon button's layout will be.
Expand All @@ -202,20 +213,20 @@ class IconButton extends StatelessWidget {
/// The padding around the button's icon. The entire padded icon will react
/// to input gestures.
///
/// This property must not be null. It defaults to 8.0 padding on all sides.
final EdgeInsetsGeometry padding;
/// This property can be null. If null, it defaults to 8.0 padding on all sides.
final EdgeInsetsGeometry? padding;

/// Defines how the icon is positioned within the IconButton.
///
/// This property must not be null. It defaults to [Alignment.center].
/// This property can be null. If null, it defaults to [Alignment.center].
///
/// See also:
///
/// * [Alignment], a class with convenient constants typically used to
/// specify an [AlignmentGeometry].
/// * [AlignmentDirectional], like [Alignment] for specifying alignments
/// relative to text direction.
final AlignmentGeometry alignment;
final AlignmentGeometry? alignment;

/// The splash radius.
///
Expand Down Expand Up @@ -353,7 +364,7 @@ class IconButton extends StatelessWidget {
/// See also:
///
/// * [Feedback] for providing platform-specific feedback to certain actions.
final bool enableFeedback;
final bool? enableFeedback;

/// Optional size constraints for the button.
///
Expand Down Expand Up @@ -465,6 +476,7 @@ class IconButton extends StatelessWidget {
Size? minimumSize,
Size? fixedSize,
Size? maximumSize,
double? iconSize,
BorderSide? side,
OutlinedBorder? shape,
EdgeInsetsGeometry? padding,
Expand Down Expand Up @@ -501,6 +513,7 @@ class IconButton extends StatelessWidget {
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),
fixedSize: ButtonStyleButton.allOrNull<Size>(fixedSize),
maximumSize: ButtonStyleButton.allOrNull<Size>(maximumSize),
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
side: ButtonStyleButton.allOrNull<BorderSide>(side),
shape: ButtonStyleButton.allOrNull<OutlinedBorder>(shape),
mouseCursor: mouseCursor,
Expand All @@ -516,25 +529,6 @@ class IconButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
if (!theme.useMaterial3) {
assert(debugCheckHasMaterial(context));
}

Color? currentColor;
if (onPressed != null) {
currentColor = color;
} else {
currentColor = disabledColor ?? theme.disabledColor;
}

final VisualDensity effectiveVisualDensity = visualDensity ?? theme.visualDensity;

final BoxConstraints unadjustedConstraints = constraints ?? const BoxConstraints(
minWidth: _kMinButtonSize,
minHeight: _kMinButtonSize,
);
final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
final double effectiveIconSize = iconSize ?? IconTheme.of(context).size ?? 24.0;

if (theme.useMaterial3) {
final Size? minSize = constraints == null
Expand All @@ -554,6 +548,7 @@ class IconButton extends StatelessWidget {
padding: padding,
minimumSize: minSize,
maximumSize: maxSize,
iconSize: iconSize,
alignment: alignment,
enabledMouseCursor: mouseCursor,
disabledMouseCursor: mouseCursor,
Expand All @@ -568,16 +563,11 @@ class IconButton extends StatelessWidget {
effectiveIcon = selectedIcon!;
}

Widget iconButton = IconTheme.merge(
data: IconThemeData(
size: effectiveIconSize,
),
child: effectiveIcon,
);
Widget iconButton = effectiveIcon;
if (tooltip != null) {
iconButton = Tooltip(
message: tooltip,
child: iconButton,
child: effectiveIcon,
);
}

Expand All @@ -591,15 +581,36 @@ class IconButton extends StatelessWidget {
);
}

assert(debugCheckHasMaterial(context));

Color? currentColor;
if (onPressed != null) {
currentColor = color;
} else {
currentColor = disabledColor ?? theme.disabledColor;
}

final VisualDensity effectiveVisualDensity = visualDensity ?? theme.visualDensity;

final BoxConstraints unadjustedConstraints = constraints ?? const BoxConstraints(
minWidth: _kMinButtonSize,
minHeight: _kMinButtonSize,
);
final BoxConstraints adjustedConstraints = effectiveVisualDensity.effectiveConstraints(unadjustedConstraints);
final double effectiveIconSize = iconSize ?? IconTheme.of(context).size ?? 24.0;
final EdgeInsetsGeometry effectivePadding = padding ?? const EdgeInsets.all(8.0);
final AlignmentGeometry effectiveAlignment = alignment ?? Alignment.center;
final bool effectiveEnableFeedback = enableFeedback ?? true;

Widget result = ConstrainedBox(
constraints: adjustedConstraints,
child: Padding(
padding: padding,
padding: effectivePadding,
child: SizedBox(
height: effectiveIconSize,
width: effectiveIconSize,
child: Align(
alignment: alignment,
alignment: effectiveAlignment,
child: IconTheme.merge(
data: IconThemeData(
size: effectiveIconSize,
Expand Down Expand Up @@ -628,14 +639,14 @@ class IconButton extends StatelessWidget {
canRequestFocus: onPressed != null,
onTap: onPressed,
mouseCursor: mouseCursor ?? (onPressed == null ? SystemMouseCursors.basic : SystemMouseCursors.click),
enableFeedback: enableFeedback,
enableFeedback: effectiveEnableFeedback,
focusColor: focusColor ?? theme.focusColor,
hoverColor: hoverColor ?? theme.hoverColor,
highlightColor: highlightColor ?? theme.highlightColor,
splashColor: splashColor ?? theme.splashColor,
radius: splashRadius ?? math.max(
Material.defaultSplashRadius,
(effectiveIconSize + math.min(padding.horizontal, padding.vertical)) * 0.7,
(effectiveIconSize + math.min(effectivePadding.horizontal, effectivePadding.vertical)) * 0.7,
// x 0.5 for diameter -> radius and + 40% overflow derived from other Material apps.
),
child: result,
Expand Down Expand Up @@ -762,6 +773,7 @@ class _IconButtonM3 extends ButtonStyleButton {
/// * `minimumSize` - Size(40, 40)
/// * `fixedSize` - null
/// * `maximumSize` - Size.infinite
/// * `iconSize` - 24
/// * `side` - null
/// * `shape` - StadiumBorder()
/// * `mouseCursor`
Expand All @@ -778,10 +790,30 @@ class _IconButtonM3 extends ButtonStyleButton {
return _IconButtonDefaultsM3(context);
}

/// Returns null because [IconButton] doesn't have its component theme.
/// Returns the [IconButtonThemeData.style] of the closest [IconButtonTheme] ancestor.
/// The color and icon size can also be configured by the [IconTheme] if the same property
/// has a null value in [IconButtonTheme]. However, if any of the properties exist
/// in both [IconButtonTheme] and [IconTheme], [IconTheme] will be overridden.
@override
ButtonStyle? themeStyleOf(BuildContext context) {
return null;
final IconThemeData iconTheme = IconTheme.of(context);
final bool isDark = Theme.of(context).brightness == Brightness.dark;

bool isIconThemeDefault(Color? color) {
if (isDark) {
return color == kDefaultIconLightColor;
}
return color == kDefaultIconDarkColor;
}
final bool isDefaultColor = isIconThemeDefault(iconTheme.color);
final bool isDefaultSize = iconTheme.size == const IconThemeData.fallback().size;

final ButtonStyle iconThemeStyle = IconButton.styleFrom(
foregroundColor: isDefaultColor ? null : iconTheme.color,
iconSize: isDefaultSize ? null : iconTheme.size
);

return IconButtonTheme.of(context).style?.merge(iconThemeStyle) ?? iconThemeStyle;
}
}

Expand Down Expand Up @@ -969,6 +1001,10 @@ class _IconButtonDefaultsM3 extends ButtonStyle {
MaterialStateProperty<Size>? get maximumSize =>
ButtonStyleButton.allOrNull<Size>(Size.infinite);

@override
MaterialStateProperty<double>? get iconSize =>
ButtonStyleButton.allOrNull<double>(24.0);

// No default side

@override
Expand Down
Loading