import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
class CustomButton extends StatefulWidget {
const CustomButton({
Key key,
@required this.onPressed,
this.onLongPress,
this.onHighlightChanged,
this.mouseCursor,
this.fillColor,
this.focusColor,
this.hoverColor,
this.highlightColor,
this.splashColor,
this.backgroundColor = const Color(0xfff3f5f9),
this.padding = EdgeInsets.zero,
this.borderRadius = 0.0,
this.visualDensity = const VisualDensity(),
this.shape,
this.showAnimation = true,
this.animationDuration = kThemeChangeDuration,
this.clipBehavior = Clip.none,
this.focusNode,
this.autofocus = false,
MaterialTapTargetSize materialTapTargetSize,
this.child,
this.enableFeedback = true,
}) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded,
assert(padding != null),
assert(animationDuration != null),
assert(clipBehavior != null),
assert(autofocus != null),
super(key: key);
/// Called when the button is tapped or otherwise activated.
///
/// If this callback and [onLongPress] are null, then the button will be disabled.
///
/// See also:
///
/// * [enabled], which is true if the button is enabled.
final VoidCallback onPressed;
/// Called when the button is long-pressed.
///
/// If this callback and [onPressed] are null, then the button will be disabled.
///
/// See also:
///
/// * [enabled], which is true if the button is enabled.
final VoidCallback onLongPress;
/// Called by the underlying [InkWell] widget's [InkWell.onHighlightChanged]
/// callback.
///
/// If [onPressed] changes from null to non-null while a gesture is ongoing,
/// this can fire during the build phase (in which case calling
/// [State.setState] is not allowed).
final ValueChanged<bool> onHighlightChanged;
/// {@template flutter.material.button.mouseCursor}
/// The cursor for a mouse pointer when it enters or is hovering over the
/// button.
///
/// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
/// [MaterialStateProperty.resolve] is used for the following [MaterialState]s:
///
/// * [MaterialState.pressed].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [MaterialState.disabled].
///
/// If this property is null, [MaterialStateMouseCursor.clickable] will be used.
/// {@endtemplate flutter.material.button.mouseCursor}
final MouseCursor mouseCursor;
/// The color of the button's [Material].
final Color fillColor;
/// The color for the button's [Material] when it has the input focus.
final Color focusColor;
/// The color for the button's [Material] when a pointer is hovering over it.
final Color hoverColor;
/// The highlight color for the button's [InkWell].
final Color highlightColor;
/// The splash color for the button's [InkWell].
final Color splashColor;
final Color backgroundColor;
/// The internal padding for the button's [child].
final EdgeInsetsGeometry padding;
final double borderRadius;
/// Defines how compact the button's layout will be.
///
/// {@macro flutter.material.themedata.visualDensity}
///
/// See also:
///
/// * [ThemeData.visualDensity], which specifies the [visualDensity] for all widgets
/// within a [Theme].
final VisualDensity visualDensity;
/// The shape of the button's [Material].
///
/// The button's highlight and splash are clipped to this shape. If the
/// button has an elevation, then its drop shadow is defined by this shape.
///
/// If [shape] is a [MaterialStateProperty<ShapeBorder>], [MaterialStateProperty.resolve]
/// is used for the following [MaterialState]s:
///
/// * [MaterialState.pressed].
/// * [MaterialState.hovered].
/// * [MaterialState.focused].
/// * [MaterialState.disabled].
final ShapeBorder shape;
/// Defines the duration of animated changes for [shape] and [elevation].
///
/// The default value is [kThemeChangeDuration].
final Duration animationDuration;
final bool showAnimation;
/// Typically the button's label.
final Widget child;
/// 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;
/// Configures the minimum size of the tap target.
///
/// Defaults to [MaterialTapTargetSize.padded].
///
/// See also:
///
/// * [MaterialTapTargetSize], for a description of how this affects tap targets.
final MaterialTapTargetSize materialTapTargetSize;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
/// {@macro flutter.widgets.Clip}
///
/// Defaults to [Clip.none], and must not be null.
final Clip clipBehavior;
/// Whether detected gestures should provide acoustic and/or haptic feedback.
///
/// For example, on Android a tap will produce a clicking sound and a
/// long-press will produce a short vibration, when feedback is enabled.
///
/// See also:
///
/// * [Feedback] for providing platform-specific feedback to certain actions.
final bool enableFeedback;
@override
State<StatefulWidget> createState() => _CustomButtonState();
}
class _CustomButtonState extends State<CustomButton> {
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);
if (widget.onHighlightChanged != null) {
widget.onHighlightChanged(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(CustomButton 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);
}
}
double get _effectiveElevation {
// These conditionals are in order of precedence, so be careful about
// reorganizing them.
if (_disabled) {
return 0.0;
}
if (_pressed) {
return 8.0;
}
if (_hovered) {
return 4.0;
}
if (_focused) {
return 4.0;
}
return 2.0;
}
@override
Widget build(BuildContext context) {
final shape = widget.shape ?? RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(widget.borderRadius)));
final ShapeBorder effectiveShape = MaterialStateProperty.resolveAs<ShapeBorder>(shape, _states);
final Offset densityAdjustment = widget.visualDensity.baseSizeAdjustment;
final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor>(
widget.mouseCursor ?? MaterialStateMouseCursor.clickable,
_states,
);
final EdgeInsetsGeometry padding = widget.padding.add(
EdgeInsets.only(
left: densityAdjustment.dx,
top: densityAdjustment.dy,
right: densityAdjustment.dx,
bottom: densityAdjustment.dy,
),
).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity);
final Widget inkWell = InkWell(
focusNode: widget.focusNode,
canRequestFocus: widget.enabled,
onFocusChange: _handleFocusedChanged,
autofocus: widget.autofocus,
onHighlightChanged: _handleHighlightChanged,
splashColor: widget.splashColor,
highlightColor: widget.highlightColor,
focusColor: widget.focusColor,
hoverColor: widget.hoverColor,
onHover: _handleHoveredChanged,
onTap: widget.onPressed,
onLongPress: widget.onLongPress,
enableFeedback: widget.enableFeedback,
customBorder: effectiveShape,
mouseCursor: effectiveMouseCursor,
child: Container(
padding: padding,
child: widget.child,
),
);
if (widget.showAnimation == false)
return backgroundCard(inkWell);
final Widget result = Material(
elevation: _effectiveElevation,
shape: effectiveShape,
color: widget.fillColor,
type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
animationDuration: widget.animationDuration,
clipBehavior: widget.clipBehavior,
child: inkWell,
);
Size minSize;
switch (widget.materialTapTargetSize) {
case MaterialTapTargetSize.padded:
minSize = Size(
densityAdjustment.dx,
densityAdjustment.dy,
);
assert(minSize.width >= 0.0);
assert(minSize.height >= 0.0);
break;
case MaterialTapTargetSize.shrinkWrap:
minSize = Size.zero;
break;
}
return backgroundCard(result);
}
Widget backgroundCard(Widget child) {
return Container(
child: child,
decoration: BoxDecoration(
borderRadius:BorderRadius.all(Radius.circular(widget.borderRadius)), //3像素圆角
color: widget.backgroundColor,
),);
}
}