Skip to content

Instantly share code, notes, and snippets.

@HansMuller
Created May 11, 2020 18:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save HansMuller/68835e67ea588a15f7234f8129bb05c6 to your computer and use it in GitHub Desktop.
Save HansMuller/68835e67ea588a15f7234f8129bb05c6 to your computer and use it in GitHub Desktop.
// 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.
// For more information see "Updating the Material Buttons and their Themes #54776"
// https://github.com/flutter/flutter/issues/54776
import 'dart:math' as math;
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/material.dart';
// To simplify running the flutter_buttons repo demo in DartPad the repo's
// contents have been inserted below. In an ordinary app it would be imported using:
// import 'package:flutter_buttons/flutter_buttons.dart';
// ----------------------------------------------------------------------------
// lib/button_style.dart
class ButtonStyle with Diagnosticable {
const ButtonStyle({
this.textStyle,
this.backgroundColor,
this.foregroundColor,
this.overlayColor,
this.elevation,
this.padding,
this.minimumSize,
this.side,
this.shape,
this.visualDensity,
this.tapTargetSize,
});
final MaterialStateProperty<TextStyle> textStyle;
final MaterialStateProperty<Color> backgroundColor;
final MaterialStateProperty<Color> foregroundColor;
final MaterialStateProperty<Color> overlayColor;
final MaterialStateProperty<double> elevation;
final MaterialStateProperty<EdgeInsetsGeometry> padding;
final MaterialStateProperty<Size> minimumSize;
final MaterialStateProperty<BorderSide> side;
final MaterialStateProperty<ShapeBorder> shape;
final VisualDensity visualDensity;
final MaterialTapTargetSize tapTargetSize;
/// Returns a copy of this ButtonStyle with the given fields replaced with
/// the new values.
ButtonStyle copyWith({
MaterialStateProperty<TextStyle> textStyle,
MaterialStateProperty<Color> backgroundColor,
MaterialStateProperty<Color> foregroundColor,
MaterialStateProperty<Color> overlayColor,
MaterialStateProperty<double> elevation,
MaterialStateProperty<EdgeInsetsGeometry> padding,
MaterialStateProperty<Size> minimumSize,
MaterialStateProperty<BorderSide> side,
MaterialStateProperty<ShapeBorder> shape,
VisualDensity visualDensity,
MaterialTapTargetSize tapTargetSize,
}) {
return ButtonStyle(
textStyle: textStyle ?? this.textStyle,
backgroundColor: backgroundColor ?? this.backgroundColor,
foregroundColor: foregroundColor ?? this.foregroundColor,
overlayColor: overlayColor ?? this.overlayColor,
elevation: elevation ?? this.elevation,
padding: padding ?? this.padding,
minimumSize: minimumSize ?? this.minimumSize,
side: side ?? this.side,
shape: shape ?? this.shape,
visualDensity: visualDensity ?? this.visualDensity,
tapTargetSize: tapTargetSize ?? this.tapTargetSize,
);
}
/// Returns a copy of this ButtonStyle where the non-null fields in [style]
/// have replaced the corresponding fields in this ButtonStyle.
ButtonStyle merge(ButtonStyle style) {
if (style == null)
return this;
return copyWith(
textStyle: style.textStyle ?? textStyle,
backgroundColor: style.backgroundColor ?? backgroundColor,
foregroundColor: style.foregroundColor ?? foregroundColor,
overlayColor: style.overlayColor ?? overlayColor,
elevation: style.elevation ?? elevation,
padding: style.padding ?? padding,
minimumSize: style.minimumSize ?? minimumSize,
side: style.side ?? side,
shape: style.shape ?? shape,
visualDensity: style.visualDensity ?? visualDensity,
tapTargetSize: style.tapTargetSize ?? tapTargetSize,
);
}
@override
int get hashCode {
return hashValues(
textStyle,
backgroundColor,
foregroundColor,
overlayColor,
elevation,
padding,
minimumSize,
side,
shape,
visualDensity,
tapTargetSize,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is ButtonStyle
&& other.textStyle == textStyle
&& other.backgroundColor == backgroundColor
&& other.foregroundColor == foregroundColor
&& other.overlayColor == overlayColor
&& other.elevation == elevation
&& other.padding == padding
&& other.minimumSize == minimumSize
&& other.side == side
&& other.shape == shape
&& other.visualDensity == visualDensity
&& other.tapTargetSize == tapTargetSize;
}
@override
String toStringShort() {
final List <String> overrides = <String>[
if (textStyle != null) 'textStyle',
if (backgroundColor != null) 'backgroundColor',
if (foregroundColor != null) 'foregroundColor',
if (overlayColor != null) 'overlayColor',
if (elevation != null) 'elevation',
if (padding != null) 'padding',
if (minimumSize != null) 'minimumSize',
if (side != null) 'side',
if (shape != null) 'shape',
if (visualDensity != null) 'visualDensity',
if (tapTargetSize != null) 'tapTargetSize',
];
final String overridesString = overrides.isEmpty
? 'no overrides'
: 'overrides ${overrides.join(", ")}';
return '${super.toStringShort()}($overridesString)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<MaterialStateProperty<TextStyle>>('textStyle', textStyle, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color>>('backgroundColor', backgroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color>>('foregroundColor', foregroundColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color>>('overlayColor', overlayColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<double>>('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<EdgeInsetsGeometry>>('padding', padding, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Size>>('minimumSize', minimumSize, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<BorderSide>>('side', side, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<ShapeBorder>>('shape', shape, defaultValue: null));
properties.add(DiagnosticsProperty<VisualDensity>('visualDensity', visualDensity, defaultValue: null));
properties.add(EnumProperty<MaterialTapTargetSize>('tapTargetSize', tapTargetSize, defaultValue: null));
}
static ButtonStyle lerp(ButtonStyle a, ButtonStyle b, double t) {
assert (t != null);
if (a == null && b == null)
return null;
return ButtonStyle(
textStyle: _lerpTextStyles(a?.textStyle, b?.textStyle, t),
backgroundColor: _lerpColors(a?.backgroundColor, b?.backgroundColor, t),
foregroundColor: _lerpColors(a?.foregroundColor, b?.foregroundColor, t),
overlayColor: _lerpColors(a?.overlayColor, b?.overlayColor, t),
elevation: _lerpDoubles(a?.elevation, b?.elevation, t),
padding: _lerpInsets(a?.padding, b?.padding, t),
minimumSize: _lerpSizes(a?.minimumSize, b?.minimumSize, t),
side: _lerpSides(a?.side, b?.side, t),
shape: _lerpShapes(a?.shape, b?.shape, t),
visualDensity: t < 0.5 ? a.visualDensity : b.visualDensity,
tapTargetSize: t < 0.5 ? a.tapTargetSize : b.tapTargetSize,
);
}
static MaterialStateProperty<TextStyle> _lerpTextStyles(MaterialStateProperty<TextStyle> a, MaterialStateProperty<TextStyle> b, double t) {
if (a == null && b == null)
return null;
return _LerpTextStyles(a, b, t);
}
static MaterialStateProperty<Color> _lerpColors(MaterialStateProperty<Color> a, MaterialStateProperty<Color> b, double t) {
if (a == null && b == null)
return null;
return _LerpColors(a, b, t);
}
static MaterialStateProperty<double> _lerpDoubles(MaterialStateProperty<double> a, MaterialStateProperty<double> b, double t) {
if (a == null && b == null)
return null;
return _LerpDoubles(a, b, t);
}
static MaterialStateProperty<EdgeInsetsGeometry> _lerpInsets(MaterialStateProperty<EdgeInsetsGeometry> a, MaterialStateProperty<EdgeInsetsGeometry> b, double t) {
if (a == null && b == null)
return null;
return _LerpInsets(a, b, t);
}
static MaterialStateProperty<Size> _lerpSizes(MaterialStateProperty<Size> a, MaterialStateProperty<Size> b, double t) {
if (a == null && b == null)
return null;
return _LerpSizes(a, b, t);
}
static MaterialStateProperty<BorderSide> _lerpSides(MaterialStateProperty<BorderSide> a, MaterialStateProperty<BorderSide> b, double t) {
if (a == null && b == null)
return null;
return _LerpSides(a, b, t);
}
static MaterialStateProperty<ShapeBorder> _lerpShapes(MaterialStateProperty<ShapeBorder> a, MaterialStateProperty<ShapeBorder> b, double t) {
if (a == null && b == null)
return null;
return _LerpShapes(a, b, t);
}
}
class _LerpTextStyles implements MaterialStateProperty<TextStyle> {
const _LerpTextStyles(this.a, this.b, this.t);
final MaterialStateProperty<TextStyle> a;
final MaterialStateProperty<TextStyle> b;
final double t;
@override
TextStyle resolve(Set<MaterialState> states) {
final TextStyle resolvedA = a?.resolve(states);
final TextStyle resolvedB = b?.resolve(states);
return TextStyle.lerp(resolvedA, resolvedB, t);
}
}
class _LerpColors implements MaterialStateProperty<Color> {
const _LerpColors(this.a, this.b, this.t);
final MaterialStateProperty<Color> a;
final MaterialStateProperty<Color> b;
final double t;
@override
Color resolve(Set<MaterialState> states) {
final Color resolvedA = a?.resolve(states);
final Color resolvedB = b?.resolve(states);
return Color.lerp(resolvedA, resolvedB, t);
}
}
class _LerpDoubles implements MaterialStateProperty<double> {
const _LerpDoubles(this.a, this.b, this.t);
final MaterialStateProperty<double> a;
final MaterialStateProperty<double> b;
final double t;
@override
double resolve(Set<MaterialState> states) {
final double resolvedA = a?.resolve(states);
final double resolvedB = b?.resolve(states);
return lerpDouble(resolvedA, resolvedB, t);
}
}
class _LerpInsets implements MaterialStateProperty<EdgeInsetsGeometry> {
const _LerpInsets(this.a, this.b, this.t);
final MaterialStateProperty<EdgeInsetsGeometry> a;
final MaterialStateProperty<EdgeInsetsGeometry> b;
final double t;
@override
EdgeInsetsGeometry resolve(Set<MaterialState> states) {
final EdgeInsetsGeometry resolvedA = a?.resolve(states);
final EdgeInsetsGeometry resolvedB = b?.resolve(states);
return EdgeInsetsGeometry.lerp(resolvedA, resolvedB, t);
}
}
class _LerpSizes implements MaterialStateProperty<Size> {
const _LerpSizes(this.a, this.b, this.t);
final MaterialStateProperty<Size> a;
final MaterialStateProperty<Size> b;
final double t;
@override
Size resolve(Set<MaterialState> states) {
final Size resolvedA = a?.resolve(states);
final Size resolvedB = b?.resolve(states);
return Size.lerp(resolvedA, resolvedB, t);
}
}
class _LerpSides implements MaterialStateProperty<BorderSide> {
const _LerpSides(this.a, this.b, this.t);
final MaterialStateProperty<BorderSide> a;
final MaterialStateProperty<BorderSide> b;
final double t;
@override
BorderSide resolve(Set<MaterialState> states) {
final BorderSide resolvedA = a?.resolve(states);
final BorderSide resolvedB = b?.resolve(states);
return BorderSide.lerp(resolvedA, resolvedB, t);
}
}
class _LerpShapes implements MaterialStateProperty<ShapeBorder> {
const _LerpShapes(this.a, this.b, this.t);
final MaterialStateProperty<ShapeBorder> a;
final MaterialStateProperty<ShapeBorder> b;
final double t;
@override
ShapeBorder resolve(Set<MaterialState> states) {
final ShapeBorder resolvedA = a?.resolve(states);
final ShapeBorder resolvedB = b?.resolve(states);
return ShapeBorder.lerp(resolvedA, resolvedB, t);
}
}
// lib/text_button_theme.dart
class TextButtonThemeData with Diagnosticable {
const TextButtonThemeData({ this.style });
final ButtonStyle style;
/// Linearly interpolate between two text button themes.
static TextButtonThemeData lerp(TextButtonThemeData a, TextButtonThemeData b, double t) {
assert (t != null);
if (a == null && b == null)
return null;
return TextButtonThemeData(
style: ButtonStyle.lerp(a?.style, b?.style, t),
);
}
@override
int get hashCode {
return style.hashCode;
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is TextButtonThemeData && other.style == style;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null));
}
}
class TextButtonTheme extends InheritedTheme {
const TextButtonTheme({
Key key,
@required this.data,
Widget child,
}) : assert(data != null), super(key: key, child: child);
final TextButtonThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [TextButtonsTheme] widget, then
/// [ThemeData.textButtonTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// TextButtonTheme theme = TextButtonTheme.of(context);
/// ```
static TextButtonThemeData of(BuildContext context) {
final TextButtonTheme buttonTheme = context.dependOnInheritedWidgetOfExactType<TextButtonTheme>();
return buttonTheme?.data;
}
@override
Widget wrap(BuildContext context, Widget child) {
final TextButtonTheme ancestorTheme = context.findAncestorWidgetOfExactType<TextButtonTheme>();
return identical(this, ancestorTheme) ? child : TextButtonTheme(data: data, child: child);
}
@override
bool updateShouldNotify(TextButtonTheme oldWidget) => data != oldWidget.data;
}
// lib/contained_button_theme.dart
class ContainedButtonThemeData with Diagnosticable {
const ContainedButtonThemeData({ this.style });
final ButtonStyle style;
/// Linearly interpolate between two text button themes.
static ContainedButtonThemeData lerp(ContainedButtonThemeData a, ContainedButtonThemeData b, double t) {
assert (t != null);
if (a == null && b == null)
return null;
return ContainedButtonThemeData(
style: ButtonStyle.lerp(a?.style, b?.style, t),
);
}
@override
int get hashCode {
return style.hashCode;
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is ContainedButtonThemeData && other.style == style;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null));
}
}
class ContainedButtonTheme extends InheritedTheme {
const ContainedButtonTheme({
Key key,
@required this.data,
Widget child,
}) : assert(data != null), super(key: key, child: child);
final ContainedButtonThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [ContainedButtonsTheme] widget, then
/// [ThemeData.textButtonTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// ContainedButtonTheme theme = ContainedButtonTheme.of(context);
/// ```
static ContainedButtonThemeData of(BuildContext context) {
final ContainedButtonTheme buttonTheme = context.dependOnInheritedWidgetOfExactType<ContainedButtonTheme>();
return buttonTheme?.data;
}
@override
Widget wrap(BuildContext context, Widget child) {
final ContainedButtonTheme ancestorTheme = context.findAncestorWidgetOfExactType<ContainedButtonTheme>();
return identical(this, ancestorTheme) ? child : ContainedButtonTheme(data: data, child: child);
}
@override
bool updateShouldNotify(ContainedButtonTheme oldWidget) => data != oldWidget.data;
}
// lib/outlined_button_theme.dart
class OutlinedButtonThemeData with Diagnosticable {
const OutlinedButtonThemeData({ this.style });
final ButtonStyle style;
/// Linearly interpolate between two text button themes.
static OutlinedButtonThemeData lerp(OutlinedButtonThemeData a, OutlinedButtonThemeData b, double t) {
assert (t != null);
if (a == null && b == null)
return null;
return OutlinedButtonThemeData(
style: ButtonStyle.lerp(a?.style, b?.style, t),
);
}
@override
int get hashCode {
return style.hashCode;
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is OutlinedButtonThemeData && other.style == style;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<ButtonStyle>('style', style, defaultValue: null));
}
}
class OutlinedButtonTheme extends InheritedTheme {
const OutlinedButtonTheme({
Key key,
@required this.data,
Widget child,
}) : assert(data != null), super(key: key, child: child);
final OutlinedButtonThemeData data;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [OutlinedButtonsTheme] widget, then
/// [ThemeData.textButtonTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// OutlinedButtonTheme theme = OutlinedButtonTheme.of(context);
/// ```
static OutlinedButtonThemeData of(BuildContext context) {
final OutlinedButtonTheme buttonTheme = context.dependOnInheritedWidgetOfExactType<OutlinedButtonTheme>();
return buttonTheme?.data;
}
@override
Widget wrap(BuildContext context, Widget child) {
final OutlinedButtonTheme ancestorTheme = context.findAncestorWidgetOfExactType<OutlinedButtonTheme>();
return identical(this, ancestorTheme) ? child : OutlinedButtonTheme(data: data, child: child);
}
@override
bool updateShouldNotify(OutlinedButtonTheme oldWidget) => data != oldWidget.data;
}
// button_style_buttons.dart
// Temporary proxy for TBD static MaterialStateProperty<T>.all(T value)
class MaterialStatePropertyAll<T> implements MaterialStateProperty<T> {
const MaterialStatePropertyAll(this.value);
final T value;
@override
T resolve(Set<MaterialState> states) => value;
@override
String toString() {
return '$runtimeType($value)';
}
}
// Temporary proxy for TBD ShapeBorder.withSide(BorderSide side)
shapeBorderWithSide(ShapeBorder shape, BorderSide side) {
if (shape == null)
return shape;
if (shape is RoundedRectangleBorder) {
return RoundedRectangleBorder(
borderRadius: shape.borderRadius,
side: side,
);
}
if (shape is BeveledRectangleBorder) {
return BeveledRectangleBorder(
borderRadius: shape.borderRadius,
side: side,
);
}
if (shape is StadiumBorder) {
return StadiumBorder(
side: side,
);
}
if (shape is CircleBorder) {
return CircleBorder(
side: side,
);
}
if (shape is ContinuousRectangleBorder) {
return ContinuousRectangleBorder(
borderRadius: shape.borderRadius,
side: side,
);
}
if (shape is CircleBorder) {
return CircleBorder(
side: side,
);
}
return shape;
}
abstract class _ButtonStyleButton extends StatefulWidget {
const _ButtonStyleButton({
Key key,
@required this.onPressed,
@required this.onLongPress,
@required this.style,
@required this.focusNode,
@required this.autofocus,
@required this.clipBehavior,
@required this.enableFeedback,
@required this.animationDuration,
@required this.child,
}) : assert(autofocus != null),
assert(clipBehavior != null),
assert(enableFeedback != null),
assert(animationDuration != null),
super(key: key);
final VoidCallback onPressed;
final VoidCallback onLongPress;
final ButtonStyle style;
final Clip clipBehavior;
final FocusNode focusNode;
final bool autofocus;
final bool enableFeedback;
final Duration animationDuration;
final Widget child;
// Returns a ButtonStyle that's based on the Theme's
// textTheme and colorScheme. Concrete button subclasses must
// resolve the button's actual visual parameters by combining this
// style with the widget's style and the button theme's style.
//
// Defined here rather than in the State subclass to ensure that
// the default style can only depend on the BuildContext.
ButtonStyle _defaultStyleOf(BuildContext context);
/// Whether the button is enabled or disabled.
///
/// Buttons are disabled by default. To enable a button, set its [onPressed]
/// or [onLongPress] properties to a non-null value.
bool get enabled => onPressed != null || onLongPress != null;
}
abstract class _ButtonStyleState<T extends _ButtonStyleButton> extends State<T> {
final Set<MaterialState> _states = <MaterialState>{};
bool get _hovered => _states.contains(MaterialState.hovered);
bool get _focused => _states.contains(MaterialState.focused);
bool get _pressed => _states.contains(MaterialState.pressed);
bool get _disabled => _states.contains(MaterialState.disabled);
void _updateState(MaterialState state, bool value) {
value ? _states.add(state) : _states.remove(state);
}
void _handleHighlightChanged(bool value) {
if (_pressed != value) {
setState(() {
_updateState(MaterialState.pressed, value);
});
}
}
void _handleHoveredChanged(bool value) {
if (_hovered != value) {
setState(() {
_updateState(MaterialState.hovered, value);
});
}
}
void _handleFocusedChanged(bool value) {
if (_focused != value) {
setState(() {
_updateState(MaterialState.focused, value);
});
}
}
@override
void initState() {
super.initState();
_updateState(MaterialState.disabled, !widget.enabled);
}
@override
void didUpdateWidget(T oldWidget) {
super.didUpdateWidget(oldWidget);
_updateState(MaterialState.disabled, !widget.enabled);
// If the button is disabled while a press gesture is currently ongoing,
// InkWell makes a call to handleHighlightChanged. This causes an exception
// because it calls setState in the middle of a build. To preempt this, we
// manually update pressed to false when this situation occurs.
if (_disabled && _pressed) {
_handleHighlightChanged(false);
}
}
T _resolve<T>(
MaterialStateProperty<T> widgetValue,
MaterialStateProperty<T> themeValue,
MaterialStateProperty<T> defaultValue)
{
assert(defaultValue != null);
return widgetValue?.resolve(_states) ?? themeValue?.resolve(_states) ?? defaultValue.resolve(_states);
}
ButtonStyle themeStyleFor(BuildContext context);
@override
Widget build(BuildContext context) {
final ButtonStyle widgetStyle = widget.style;
final ButtonStyle themeStyle = themeStyleFor(context);
final ButtonStyle defaultStyle = widget._defaultStyleOf(context);
final TextStyle resolvedTextStyle = _resolve<TextStyle>(
widgetStyle?.textStyle, themeStyle?.textStyle, defaultStyle.textStyle,
);
final Color resolvedBackgroundColor = _resolve<Color>(
widgetStyle?.backgroundColor, themeStyle?.backgroundColor, defaultStyle.backgroundColor,
);
final Color resolvedForegroundColor = _resolve<Color>(
widgetStyle?.foregroundColor, themeStyle?.foregroundColor, defaultStyle.foregroundColor,
);
final double resolvedElevation = _resolve<double>(
widgetStyle?.elevation, themeStyle?.elevation, defaultStyle.elevation,
);
final EdgeInsetsGeometry resolvedPadding = _resolve<EdgeInsetsGeometry>(
widgetStyle?.padding, themeStyle?.padding, defaultStyle.padding,
);
final Size resolvedMinimumSize = _resolve<Size>(
widgetStyle?.minimumSize, themeStyle?.minimumSize, defaultStyle.minimumSize,
);
final BorderSide resolvedSide = _resolve<BorderSide>(
widgetStyle?.side, themeStyle?.side, defaultStyle.side,
);
final ShapeBorder resolvedShape = _resolve<ShapeBorder>(
widgetStyle?.shape, themeStyle?.shape, defaultStyle.shape,
);
Color resolveOverlayColor(MaterialState state) {
final Set<MaterialState> states = <MaterialState>{ state };
return widgetStyle?.overlayColor?.resolve(states)
?? themeStyle?.overlayColor?.resolve(states)
?? defaultStyle.overlayColor.resolve(states);
}
final Color pressedColor = resolveOverlayColor(MaterialState.pressed);
final Color hoveredColor = resolveOverlayColor(MaterialState.hovered);
final Color focusedColor = resolveOverlayColor(MaterialState.focused);
final VisualDensity resolvedVisualDensity = widgetStyle?.visualDensity ?? defaultStyle.visualDensity;
final MaterialTapTargetSize resolvedTapTargetSize = widgetStyle?.tapTargetSize ?? defaultStyle.tapTargetSize;
final Offset densityAdjustment = resolvedVisualDensity.baseSizeAdjustment;
final BoxConstraints effectiveConstraints = resolvedVisualDensity.effectiveConstraints(
BoxConstraints(
minWidth: resolvedMinimumSize.width,
minHeight: resolvedMinimumSize.height,
),
);
final EdgeInsetsGeometry padding = resolvedPadding.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: effectiveConstraints,
child: Material(
elevation: resolvedElevation,
textStyle: resolvedTextStyle?.copyWith(color: resolvedForegroundColor),
shape: shapeBorderWithSide(resolvedShape, resolvedSide), // resolvedShape?.withSide(resolvedSide)
color: resolvedBackgroundColor,
type: resolvedBackgroundColor == null ? MaterialType.transparency : MaterialType.button,
animationDuration: widget.animationDuration,
clipBehavior: widget.clipBehavior,
child: InkWell(
onTap: widget.onPressed,
onLongPress: widget.onLongPress,
onHighlightChanged: _handleHighlightChanged,
onHover: _handleHoveredChanged,
enableFeedback: widget.enableFeedback,
focusNode: widget.focusNode,
canRequestFocus: widget.enabled,
onFocusChange: _handleFocusedChanged,
autofocus: widget.autofocus,
splashFactory: InkRipple.splashFactory,
highlightColor: Colors.transparent,
splashColor: pressedColor,
focusColor: focusedColor,
hoverColor: hoveredColor,
customBorder: resolvedShape,
child: IconTheme.merge(
data: IconThemeData(color: resolvedForegroundColor),
child: Padding(
padding: padding,
child: Center(
widthFactor: 1.0,
heightFactor: 1.0,
child: widget.child,
),
),
),
),
),
);
Size minSize;
switch (resolvedTapTargetSize) {
case MaterialTapTargetSize.padded:
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;
break;
}
return Semantics(
container: true,
button: true,
enabled: widget.enabled,
child: _InputPadding(
minSize: minSize,
child: result,
),
);
}
}
/// A material design "Text Button".
///
/// A text button is a text label displayed on a (zero elevation) [Material]
/// widget that reacts to touches by filling with color.
///
/// Use text buttons on toolbars, in dialogs, or inline with other content but
/// offset from that content with padding so that the button's presence is
/// obvious. Text buttons intentionally do not have visible borders and must
/// therefore rely on their position relative to other content for context. In
/// dialogs and cards, they should be grouped together in one of the bottom
/// corners. Avoid using text buttons where they would blend in with other
/// content, for example in the middle of lists.
///
/// Material design text buttons have an all-caps label, some internal padding,
/// and some defined dimensions. To have a part of your application be
/// interactive, with ink splashes, without also committing to these stylistic
/// choices, consider using [InkWell] instead.
///
/// If the [onPressed] and [onLongPress] callbacks are null, then this button will be disabled,
/// will not react to touch, and will be colored as specified by
/// the [disabledColor] property instead of the [color] property. If you are
/// trying to change the button's [color] and it is not having any effect, check
/// that you are passing a non-null [onPressed] handler.
///
/// Text buttons have a minimum size of 88.0 by 36.0 which can be overridden
/// with [ButtonTheme].
///
/// The [clipBehavior] argument must not be null.
///
/// {@tool snippet}
///
/// This example shows a simple [TextButton].
///
/// ![A simple TextButton](https://flutter.github.io/assets-for-api-docs/assets/material/text_button.png)
///
/// ```dart
/// TextButton(
/// onPressed: () {
/// /*...*/
/// },
/// child: Text(
/// "Text Button",
/// ),
/// )
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [ContainedButton], a filled button whose material elevates when pressed.
/// * [DropdownButton], which offers the user a choice of a number of options.
/// * [SimpleDialogOption], which is used in [SimpleDialog]s.
/// * [IconButton], to create buttons that just contain icons.
/// * [InkWell], which implements the ink splash part of a text button.
/// * <https://material.io/design/components/buttons.html>
class TextButton extends _ButtonStyleButton {
/// Create a TextButton.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const TextButton({
Key key,
@required VoidCallback onPressed,
VoidCallback onLongPress,
ButtonStyle style,
FocusNode focusNode,
bool autofocus = false,
Clip clipBehavior = Clip.none,
bool enableFeedback = true,
Duration animationDuration = kThemeChangeDuration,
@required Widget child,
}) : super(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
style: style,
focusNode: focusNode,
autofocus: autofocus,
clipBehavior: clipBehavior,
enableFeedback: enableFeedback,
animationDuration: animationDuration,
child: child,
);
factory TextButton.icon({
Key key,
@required VoidCallback onPressed,
VoidCallback onLongPress,
ButtonStyle style,
FocusNode focusNode,
bool autofocus = false,
Clip clipBehavior = Clip.none,
bool enableFeedback = true,
Duration animationDuration = kThemeChangeDuration,
@required Widget icon,
@required Widget label,
}) {
assert(icon != null);
assert(label != null);
return TextButton(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
style: style,
focusNode: focusNode,
autofocus: autofocus,
clipBehavior: clipBehavior,
enableFeedback: enableFeedback,
animationDuration: animationDuration,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
);
}
@override
_TextButtonState createState() => _TextButtonState();
static ButtonStyle styleFrom({
Color primary,
Color onSurface,
Color backgroundColor,
TextStyle textStyle,
double elevation,
EdgeInsetsGeometry padding,
Size minimumSize,
BorderSide side,
ShapeBorder shape,
VisualDensity visualDensity,
MaterialTapTargetSize tapTargetSize,
}) {
final MaterialStateProperty<Color> foregroundColor = (onSurface == null && primary == null)
? null
: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return onSurface?.withOpacity(0.38);
return primary;
},
);
final MaterialStateProperty<Color> overlayColor = (primary == null)
? null
: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return primary?.withOpacity(0.04);
if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed))
return primary?.withOpacity(0.12);
return null;
}
);
return ButtonStyle(
textStyle: MaterialStatePropertyAll<TextStyle>(textStyle),
foregroundColor: foregroundColor,
backgroundColor: MaterialStatePropertyAll<Color>(backgroundColor),
overlayColor: overlayColor,
elevation: MaterialStatePropertyAll<double>(elevation),
padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(padding),
minimumSize: MaterialStatePropertyAll<Size>(minimumSize),
side: MaterialStatePropertyAll<BorderSide>(side),
shape: MaterialStatePropertyAll<ShapeBorder>(shape),
visualDensity: visualDensity,
tapTargetSize: tapTargetSize,
);
}
@override
ButtonStyle _defaultStyleOf(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
return styleFrom(
primary: colorScheme.primary,
onSurface: colorScheme.onSurface,
backgroundColor: Colors.transparent,
textStyle: theme.textTheme.button,
elevation: 0,
padding: EdgeInsets.all(8),
minimumSize: Size(0, 36),
side: BorderSide.none,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
visualDensity: theme.visualDensity,
tapTargetSize: theme.materialTapTargetSize,
);
}
}
class _TextButtonState extends _ButtonStyleState<TextButton> {
@override
ButtonStyle themeStyleFor(BuildContext context) {
return TextButtonTheme.of(context)?.style;
}
}
/// A material design "contained button".
///
/// A contained button is based on a [Material] widget whose [Material.elevation]
/// increases when the button is pressed.
///
/// Use contained buttons to add dimension to otherwise mostly flat layouts, e.g.
/// in long busy lists of content, or in wide spaces. Avoid using contained buttons
/// on already-contained content such as dialogs or cards.
///
/// If [onPressed] and [onLongPress] callbacks are null, then the button will be disabled and by
/// default will resemble a flat button in the [disabledColor]. If you are
/// trying to change the button's [color] and it is not having any effect, check
/// that you are passing a non-null [onPressed] or [onLongPress] callbacks.
///
/// If you want an ink-splash effect for taps, but don't want to use a button,
/// consider using [InkWell] directly.
///
/// Contained buttons have a minimum size of 88.0 by 36.0 which can be overridden
/// with [ButtonTheme].
///
/// {@tool dartpad --template=stateless_widget_scaffold}
///
/// This sample shows how to render a disabled ContainedButton, an enabled ContainedButton
/// and lastly a ContainedButton with gradient background.
///
/// ![Three contained buttons, one enabled, another disabled, and the last one
/// styled with a blue gradient background](https://flutter.github.io/assets-for-api-docs/assets/material/contained_button.png)
///
/// ```dart
/// Widget build(BuildContext context) {
/// return Center(
/// child: Column(
/// mainAxisSize: MainAxisSize.min,
/// children: <Widget>[
/// const ContainedButton(
/// onPressed: null,
/// style: ContainedButton.styleFrom(
/// textStyle: TextStyle(fontSize: 20),
/// ),
/// child: Text('Disabled Button'),
/// ),
/// const SizedBox(height: 30),
/// const ContainedButton(
/// onPressed: () {},
/// style: ContainedButton.styleFrom(
/// textStyle: TextStyle(fontSize: 20),
/// ),
/// child: Text('Enabled Button'),
/// ),
/// const SizedBox(height: 30),
/// ContainedButton(
/// onPressed: () {},
/// style: ContainedButton.styleFrom(
/// textStyle: TextStyle(fontSize: 20),
/// textColor: Colors.white,
/// const EdgeInsets.all(0.0),
/// ),
/// child: Container(
/// decoration: const BoxDecoration(
/// gradient: LinearGradient(
/// colors: <Color>[
/// Color(0xFF0D47A1),
/// Color(0xFF1976D2),
/// Color(0xFF42A5F5),
/// ],
/// ),
/// ),
/// padding: const EdgeInsets.all(10.0),
/// child: const Text('Gradient Button'),
/// ),
/// ),
/// ],
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [TextButton], a material design button without a shadow.
/// * [DropdownButton], a button that shows options to select from.
/// * [FloatingActionButton], the round button in material applications.
/// * [IconButton], to create buttons that just contain icons.
/// * [InkWell], which implements the ink splash part of a flat button.
/// * <https://material.io/design/components/buttons.html>
class ContainedButton extends _ButtonStyleButton {
/// Create a ContainedButton.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const ContainedButton({
Key key,
@required VoidCallback onPressed,
VoidCallback onLongPress,
ButtonStyle style,
FocusNode focusNode,
bool autofocus = false,
Clip clipBehavior = Clip.none,
bool enableFeedback = true,
Duration animationDuration = kThemeChangeDuration,
@required Widget child,
}) : super(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
style: style,
focusNode: focusNode,
autofocus: autofocus,
clipBehavior: clipBehavior,
enableFeedback: enableFeedback,
animationDuration: animationDuration,
child: child,
);
factory ContainedButton.icon({
Key key,
@required VoidCallback onPressed,
VoidCallback onLongPress,
ButtonStyle style,
FocusNode focusNode,
bool autofocus = false,
Clip clipBehavior = Clip.none,
bool enableFeedback = true,
Duration animationDuration = kThemeChangeDuration,
@required Widget icon,
@required Widget label,
}) {
assert(icon != null);
assert(label != null);
const ButtonStyle paddingStyle = ButtonStyle(
padding: MaterialStatePropertyAll<EdgeInsets>(EdgeInsets.only(left: 12, right: 16)),
);
return ContainedButton(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
style: style == null ? paddingStyle : style.merge(paddingStyle),
focusNode: focusNode,
autofocus: autofocus,
clipBehavior: clipBehavior,
enableFeedback: enableFeedback,
animationDuration: animationDuration,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
);
}
@override
_ContainedButtonState createState() => _ContainedButtonState();
static ButtonStyle styleFrom({
Color primary,
Color onPrimary,
Color onSurface,
double elevation,
TextStyle textStyle,
EdgeInsetsGeometry padding,
Size minimumSize,
BorderSide side,
ShapeBorder shape,
VisualDensity visualDensity,
MaterialTapTargetSize tapTargetSize,
}) {
final MaterialStateProperty<Color> backgroundColor = (onSurface == null && primary == null)
? null
: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return onSurface?.withOpacity(0.12);
return primary;
}
);
final MaterialStateProperty<Color> foregroundColor = (onSurface == null && onPrimary == null)
? null
: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return onSurface?.withOpacity(0.38);
return onPrimary;
}
);
final MaterialStateProperty<Color> overlayColor = (onPrimary == null)
? null
: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return onPrimary?.withOpacity(0.08);
if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed))
return onPrimary?.withOpacity(0.24);
return null;
}
);
final MaterialStateProperty<double> elevationValue = (elevation == null)
? null
: MaterialStateProperty.resolveWith<double>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) return 0;
if (states.contains(MaterialState.hovered)) return elevation + 2;
if (states.contains(MaterialState.focused)) return elevation + 2;
if (states.contains(MaterialState.pressed)) return elevation + 6;
return elevation;
}
);
return ButtonStyle(
textStyle: MaterialStatePropertyAll<TextStyle>(textStyle),
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
overlayColor: overlayColor,
elevation: elevationValue,
padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(padding),
minimumSize: MaterialStatePropertyAll<Size>(minimumSize),
side: MaterialStatePropertyAll<BorderSide>(side),
shape: MaterialStatePropertyAll<ShapeBorder>(shape),
visualDensity: visualDensity,
tapTargetSize: tapTargetSize,
);
}
@override
ButtonStyle _defaultStyleOf(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
return styleFrom(
primary: colorScheme.primary,
onPrimary: colorScheme.onPrimary,
onSurface: colorScheme.onSurface,
elevation: 2,
textStyle: theme.textTheme.button,
padding: const EdgeInsets.symmetric(horizontal: 16),
minimumSize: const Size(64, 36),
side: BorderSide.none,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
visualDensity: theme.visualDensity,
tapTargetSize: theme.materialTapTargetSize,
);
}
}
class _ContainedButtonState extends _ButtonStyleState<ContainedButton> {
@override
ButtonStyle themeStyleFor(BuildContext context) {
return ContainedButtonTheme.of(context)?.style;
}
}
/// Essentially a [TextButton] with a thin grey rounded rectangle border.
///
/// If the [onPressed] or [onLongPress] callbacks are null, then the button will be disabled and by
/// default will resemble a flat button in the [disabledColor].
///
/// If you want an ink-splash effect for taps, but don't want to use a button,
/// consider using [InkWell] directly.
///
/// See also:
///
/// * [ContainedButton], a filled material design button with a shadow.
/// * [TextButton], a material design button without a shadow.
/// * [DropdownButton], a button that shows options to select from.
/// * [FloatingActionButton], the round button in material applications.
/// * [IconButton], to create buttons that just contain icons.
/// * [InkWell], which implements the ink splash part of a flat button.
/// * <https://material.io/design/components/buttons.html>
class OutlinedButton extends _ButtonStyleButton {
/// Create an OutlinedButton.
///
/// The [autofocus] and [clipBehavior] arguments must not be null.
const OutlinedButton({
Key key,
@required VoidCallback onPressed,
VoidCallback onLongPress,
ButtonStyle style,
FocusNode focusNode,
bool autofocus = false,
Clip clipBehavior = Clip.none,
bool enableFeedback = true,
Duration animationDuration = kThemeChangeDuration,
@required Widget child,
}) : super(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
style: style,
focusNode: focusNode,
autofocus: autofocus,
clipBehavior: clipBehavior,
enableFeedback: enableFeedback,
animationDuration: animationDuration,
child: child,
);
factory OutlinedButton.icon({
Key key,
@required VoidCallback onPressed,
VoidCallback onLongPress,
ButtonStyle style,
FocusNode focusNode,
bool autofocus = false,
Clip clipBehavior = Clip.none,
bool enableFeedback = true,
Duration animationDuration = kThemeChangeDuration,
@required Widget icon,
@required Widget label,
}) {
assert(icon != null);
assert(label != null);
const ButtonStyle paddingStyle = ButtonStyle(
padding: MaterialStatePropertyAll<EdgeInsets>(EdgeInsets.only(left: 12, right: 16)),
);
return OutlinedButton(
key: key,
onPressed: onPressed,
onLongPress: onLongPress,
style: style == null ? paddingStyle : style.merge(paddingStyle),
focusNode: focusNode,
autofocus: autofocus,
clipBehavior: clipBehavior,
enableFeedback: enableFeedback,
animationDuration: animationDuration,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
icon,
const SizedBox(width: 8.0),
label,
],
),
);
}
@override
_OutlinedButtonState createState() => _OutlinedButtonState();
static ButtonStyle styleFrom({
Color primary,
Color onSurface,
Color backgroundColor,
TextStyle textStyle,
double elevation,
EdgeInsetsGeometry padding,
Size minimumSize,
BorderSide side,
ShapeBorder shape,
VisualDensity visualDensity,
MaterialTapTargetSize tapTargetSize,
}) {
final MaterialStateProperty<Color> foregroundColor = (onSurface == null && primary == null)
? null
: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled))
return onSurface?.withOpacity(0.38);
return primary;
},
);
final MaterialStateProperty<Color> overlayColor = (primary == null)
? null
: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered))
return primary?.withOpacity(0.04);
if (states.contains(MaterialState.focused) || states.contains(MaterialState.pressed))
return primary?.withOpacity(0.12);
return null;
}
);
return ButtonStyle(
textStyle: MaterialStatePropertyAll<TextStyle>(textStyle),
foregroundColor: foregroundColor,
backgroundColor: MaterialStatePropertyAll<Color>(backgroundColor),
overlayColor: overlayColor,
elevation: MaterialStatePropertyAll<double>(elevation),
padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(padding),
minimumSize: MaterialStatePropertyAll<Size>(minimumSize),
side: MaterialStatePropertyAll<BorderSide>(side),
shape: MaterialStatePropertyAll<ShapeBorder>(shape),
visualDensity: visualDensity,
tapTargetSize: tapTargetSize,
);
}
@override
ButtonStyle _defaultStyleOf(BuildContext context) {
final ThemeData theme = Theme.of(context);
final ColorScheme colorScheme = theme.colorScheme;
return styleFrom(
primary: colorScheme.primary,
onSurface: colorScheme.onSurface,
backgroundColor: Colors.transparent,
textStyle: theme.textTheme.button,
elevation: 0,
padding: const EdgeInsets.symmetric(horizontal: 16),
minimumSize: Size(64, 36),
side: BorderSide(
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.12),
width: 1,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
visualDensity: theme.visualDensity,
tapTargetSize: theme.materialTapTargetSize,
);
}
}
class _OutlinedButtonState extends _ButtonStyleState<OutlinedButton> {
@override
ButtonStyle themeStyleFor(BuildContext context) {
return OutlinedButtonTheme.of(context)?.style;
}
}
/// A widget to pad the area around a [MaterialButton]'s inner [Material].
///
/// Redirect taps that occur in the padded area around the child to the center
/// of the child. This increases the size of the button and the button's
/// "tap target", but not its material or its ink splashes.
class _InputPadding extends SingleChildRenderObjectWidget {
const _InputPadding({
Key key,
Widget child,
this.minSize,
}) : super(key: key, child: child);
final Size minSize;
@override
RenderObject createRenderObject(BuildContext context) {
return _RenderInputPadding(minSize);
}
@override
void updateRenderObject(BuildContext context, covariant _RenderInputPadding renderObject) {
renderObject.minSize = minSize;
}
}
class _RenderInputPadding extends RenderShiftedBox {
_RenderInputPadding(this._minSize, [RenderBox child]) : super(child);
Size get minSize => _minSize;
Size _minSize;
set minSize(Size value) {
if (_minSize == value)
return;
_minSize = value;
markNeedsLayout();
}
@override
double computeMinIntrinsicWidth(double height) {
if (child != null)
return math.max(child.getMinIntrinsicWidth(height), minSize.width);
return 0.0;
}
@override
double computeMinIntrinsicHeight(double width) {
if (child != null)
return math.max(child.getMinIntrinsicHeight(width), minSize.height);
return 0.0;
}
@override
double computeMaxIntrinsicWidth(double height) {
if (child != null)
return math.max(child.getMaxIntrinsicWidth(height), minSize.width);
return 0.0;
}
@override
double computeMaxIntrinsicHeight(double width) {
if (child != null)
return math.max(child.getMaxIntrinsicHeight(width), minSize.height);
return 0.0;
}
@override
void performLayout() {
final BoxConstraints constraints = this.constraints;
if (child != null) {
child.layout(constraints, parentUsesSize: true);
final double height = math.max(child.size.width, minSize.width);
final double width = math.max(child.size.height, minSize.height);
size = constraints.constrain(Size(height, width));
final BoxParentData childParentData = child.parentData as BoxParentData;
childParentData.offset = Alignment.center.alongOffset(size - child.size as Offset);
} else {
size = Size.zero;
}
}
@override
bool hitTest(BoxHitTestResult result, { Offset position }) {
if (super.hitTest(result, position: position)) {
return true;
}
final Offset center = child.size.center(Offset.zero);
return result.addWithRawTransform(
transform: MatrixUtils.forceToPoint(center),
position: center,
hitTest: (BoxHitTestResult result, Offset position) {
assert(position == center);
return child.hitTest(result, position: center);
},
);
}
}
// The demo code follows. The code above the line is from the flutter_buttons
// repo. In an ordinary app it would be imported using:
// import 'package:flutter_buttons/flutter_buttons.dart';
// ----------------------------------------------------------------------------
class DialogButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
void dismissDialog() {
Navigator.of(context).pop();
}
void showDemoDialog(String message, ButtonStyle style1, [ButtonStyle style2]) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('AlertDialog Title'),
content: Text(message),
actions: <Widget>[
OutlinedButton(
style: style1,
onPressed: () { dismissDialog(); },
child: Text('Approve'),
),
OutlinedButton(
style: style2 ?? style1,
onPressed: () { dismissDialog(); },
child: Text('Really Approve'),
),
],
);
},
);
}
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ContainedButton(
onPressed: () {
showDemoDialog(
'Stadium shaped action buttons, default ouline.',
OutlinedButton.styleFrom(shape: StadiumBorder()),
);
},
child: Text('Show an AlertDialog'),
),
SizedBox(height: 16),
ContainedButton(
onPressed: () {
showDemoDialog(
'One Stadium shaped action button, with a heavy, primary color '
'outline.',
OutlinedButton.styleFrom(shape: StadiumBorder()),
OutlinedButton.styleFrom(
shape: StadiumBorder(),
side: BorderSide(
width: 2,
color: Theme.of(context).colorScheme.primary,
),
),
);
},
child: Text('Show another AlertDialog'),
),
SizedBox(height: 16),
ContainedButton(
onPressed: () {
showDemoDialog(
'Stadium shaped action buttons, with a heavy, primary color '
'outline when the button is focused or hovered',
OutlinedButton.styleFrom(
shape: StadiumBorder(),
).copyWith(
side: MaterialStateProperty.resolveWith<BorderSide>((Set<MaterialState> states) {
if (states.contains(MaterialState.hovered) || states.contains(MaterialState.focused)) {
return BorderSide(
width: 2,
color: Theme.of(context).colorScheme.primary,
);
}
return null; // defer to the default
},
)),
);
},
child: Text('Show yet another AlertDialog'),
),
]
),
);
}
}
class IndividuallySizedButtons extends StatefulWidget {
@override
_IndividuallySizedButtonsState createState() => _IndividuallySizedButtonsState();
}
class _IndividuallySizedButtonsState extends State<IndividuallySizedButtons> {
bool _textButtonFlag = false;
bool _containedButtonFlag = false;
bool _outlinedButtonFlag = false;
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
const Widget spacer = SizedBox(height: 16);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
spacer,
TextButton(
style: TextButton.styleFrom(
textStyle: _textButtonFlag ? textTheme.headline2 : textTheme.headline4,
),
onPressed: () {
setState(() {
_textButtonFlag = !_textButtonFlag;
});
},
child: Text('TEXT'),
),
spacer,
ContainedButton(
style: ContainedButton.styleFrom(
textStyle: _containedButtonFlag ? textTheme.headline2 : textTheme.headline4,
),
onPressed: () {
setState(() {
_containedButtonFlag = !_containedButtonFlag;
});
},
child: Text('CONTAINED'),
),
spacer,
OutlinedButton(
style: OutlinedButton.styleFrom(
textStyle: _outlinedButtonFlag ? textTheme.headline2 : textTheme.headline4,
),
onPressed: () {
setState(() {
_outlinedButtonFlag = !_outlinedButtonFlag;
});
},
child: Text('OUTLINED'),
),
spacer,
],
),
);
}
}
class ShapeButtons extends StatefulWidget {
@override
_ShapeButtonsState createState() => _ShapeButtonsState();
}
class _ShapeButtonsState extends State<ShapeButtons> {
int shapeIndex = 0;
@override
Widget build(BuildContext context) {
final List<ShapeBorder> buttonShapes = <ShapeBorder>[
RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
BeveledRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8)),
),
StadiumBorder(),
];
return TextButtonTheme(
data: TextButtonThemeData(
style: TextButton.styleFrom(shape: buttonShapes[shapeIndex]),
),
child: ContainedButtonTheme(
data: ContainedButtonThemeData(
style: ContainedButton.styleFrom(shape: buttonShapes[shapeIndex]),
),
child: OutlinedButtonTheme(
data: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(shape: buttonShapes[shapeIndex]),
),
child: OverflowBox(
maxWidth: double.infinity,
child: DefaultButtons(
onPressed: () {
setState(() {
shapeIndex = (shapeIndex + 1) % buttonShapes.length;
});
},
),
),
),
),
);
}
}
class TextStyleButtons extends StatefulWidget {
@override
_TextStyleButtonsState createState() => _TextStyleButtonsState();
}
class _TextStyleButtonsState extends State<TextStyleButtons> {
bool _flag = false;
@override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
final TextStyle style = _flag ? textTheme.headline1 : textTheme.headline4;
return TextButtonTheme(
data: TextButtonThemeData(
style: TextButton.styleFrom(textStyle: style),
),
child: ContainedButtonTheme(
data: ContainedButtonThemeData(
style: ContainedButton.styleFrom(textStyle: style),
),
child: OutlinedButtonTheme(
data: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(textStyle: style),
),
child: OverflowBox(
maxWidth: double.infinity,
child: DefaultButtons(
iconSize: _flag ? 64 : 24,
onPressed: () {
setState(() {
_flag = !_flag;
});
},
),
),
),
),
);
}
}
class TextColorButtons extends StatefulWidget {
@override
_TextColorButtonsState createState() => _TextColorButtonsState();
}
class _TextColorButtonsState extends State<TextColorButtons> {
int index = 0;
static const List<Color> foregroundColors = <Color>[
Colors.red,
Colors.purple,
Colors.indigo,
Colors.teal,
Colors.lime,
Colors.deepOrange,
Colors.yellow,
];
static const List<Color> backgroundColors = <Color>[
Colors.lightBlue,
Colors.yellow,
Colors.grey,
Colors.amber,
Colors.orange,
Colors.blue,
Colors.purple,
];
@override
Widget build(BuildContext context) {
final Color foregroundColor = foregroundColors[index];
final Color backgroundColor = backgroundColors[index];
return TextButtonTheme(
data: TextButtonThemeData(
style: TextButton.styleFrom(primary: foregroundColor),
),
child: ContainedButtonTheme(
data: ContainedButtonThemeData(
style: ContainedButton.styleFrom(
primary: backgroundColor,
onPrimary: foregroundColor,
),
),
child: OutlinedButtonTheme(
data: OutlinedButtonThemeData(
style: TextButton.styleFrom(primary: foregroundColor),
),
child: DefaultButtons(
onPressed: () {
setState(() {
index = (index + 1) % foregroundColors.length;
});
},
),
),
),
);
}
}
class DefaultButtons extends StatelessWidget {
const DefaultButtons({ Key key, this.onPressed, this.iconSize = 18 }) : super(key: key);
final VoidCallback onPressed;
final double iconSize;
@override
Widget build(BuildContext context) {
const Widget spacer = SizedBox(height: 4);
final Widget icon = Icon(Icons.star, size: iconSize);
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextButton(
onPressed: onPressed ?? () { },
child: Text('TEXT'),
),
spacer,
TextButton.icon(
onPressed: onPressed ?? () { },
icon: icon,
label: Text('TEXT'),
),
spacer,
TextButton.icon(
onPressed: null,
icon: icon,
label: Text('DISABLED'),
),
spacer,
ContainedButton(
onPressed: onPressed ?? () { },
child: Text('CONTAINED'),
),
spacer,
ContainedButton.icon(
onPressed: onPressed ?? () { },
icon: icon,
label: Text('CONTAINED'),
),
spacer,
ContainedButton.icon(
onPressed: null,
icon: icon,
label: Text('DISABLED'),
),
spacer,
OutlinedButton(
onPressed: onPressed ?? () { },
child: Text('OUTLINED'),
),
OutlinedButton.icon(
onPressed: onPressed ?? () { },
icon: icon,
label: Text('OUTLINED'),
),
spacer,
OutlinedButton.icon(
onPressed: null,
icon: icon,
label: Text('DISABLED'),
),
spacer,
],
),
);
}
}
class ButtonDemo {
const ButtonDemo({ Key key, this.title, this.description, this.builder });
final String title;
final String description;
final WidgetBuilder builder;
}
final List<ButtonDemo> allButtonDemos = <ButtonDemo>[
ButtonDemo(
title: 'Default Buttons',
description: 'Enabled and disabled buttons in their default configurations.',
builder: (BuildContext context) => DefaultButtons(),
),
ButtonDemo(
title: 'TextColor Buttons',
description:
'Use TextButtonTheme, ContainedButtonTheme, OutlinedButtonTheme to '
'override the text color of all buttons. The background color for '
'ContainedButtons does not change.',
builder: (BuildContext context) => TextColorButtons(),
),
ButtonDemo(
title: 'TextStyle Buttons',
description:
'Use TextButtonTheme, ContainedButtonTheme, OutlinedButtonTheme to override '
'the default text style of the buttons. Press any button to toggle the text '
'style size to an even bigger value.',
builder: (BuildContext context) => TextStyleButtons(),
),
ButtonDemo(
title: 'Individually Sized Buttons',
description:
'Sets the ButtonStyle parameter of individual buttons to override their '
'default text style/ Press any button to toggle its text text style size.',
builder: (BuildContext context) => IndividuallySizedButtons(),
),
ButtonDemo(
title: 'Button Shapes',
description:
'Use TextButtonTheme, ContainedButtonTheme, OutlinedButtonTheme to '
'override the shape all buttons.',
builder: (BuildContext context) => ShapeButtons(),
),
ButtonDemo(
title: 'Dialog Buttons',
description:
'Use ButtonStyle to configure the shape of a dialog\'s action buttons.',
builder: (BuildContext context) => DialogButtons(),
),
];
class Home extends StatefulWidget {
const Home({ Key key, this.toggleThemeMode }) : super(key: key);
final VoidCallback toggleThemeMode;
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
PageController _pageController;
int _currentPage = 0;
@override
void initState() {
super.initState();
_pageController = PageController(initialPage: _currentPage);
_pageController.addListener(pageChanged);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
void pageChanged() {
if (_pageController.hasClients) {
setState(() {
_currentPage = _pageController.page.floor();
});
}
}
void changePage(int delta) {
if (_pageController.hasClients) {
const Duration duration = Duration(milliseconds: 300);
_pageController.animateToPage(_currentPage + delta, duration: duration, curve: Curves.easeInOut);
}
}
@override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final ButtonStyle actionButtonStyle = TextButton.styleFrom(
primary: colorScheme.onPrimary,
);
return Scaffold(
appBar: AppBar(
title: Text(
allButtonDemos[_currentPage].title,
style: TextStyle(color: colorScheme.onPrimary)
),
backgroundColor: colorScheme.primary,
actions: <Widget>[
TextButton(
style: actionButtonStyle,
onPressed: widget.toggleThemeMode,
child: Icon(Icons.stars),
),
TextButton(
style: actionButtonStyle,
onPressed: () { changePage(-1); },
child: Icon(Icons.arrow_back),
),
TextButton(
style: actionButtonStyle,
onPressed: () { changePage(1); },
child: Icon(Icons.arrow_forward),
),
],
),
body: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 16, left: 16, right: 16, bottom: 8),
child: Container(
height: 98,
color: colorScheme.onSurface.withOpacity(0.1),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.all(12),
child: Text(
allButtonDemos[_currentPage].description,
maxLines: 4,
style: TextStyle(color: colorScheme.onSurface.withOpacity(0.7)),
),
),
),
),
Expanded(
child: PageView.builder(
controller: _pageController,
itemCount: allButtonDemos.length,
itemBuilder: (BuildContext context, int index) {
return allButtonDemos[index].builder(context);
},
),
),
],
),
);
}
}
class App extends StatefulWidget {
@override
_AppState createState() => _AppState();
}
class _AppState extends State<App> {
ThemeMode _themeMode = ThemeMode.light;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.from(colorScheme: ColorScheme.light()),
darkTheme: ThemeData.from(colorScheme: ColorScheme.dark()),
themeMode: _themeMode,
debugShowCheckedModeBanner: false,
home: Home(
toggleThemeMode: () {
setState(() {
_themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
});
}
),
);
}
}
void main() {
runApp(App());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment