Adds onHover and onLongPress to IconButton widget (#160032)
Adds `onHover` and `onLongPress` to `IconButton` widget fix #159972 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
This commit is contained in:
parent
603484f1bf
commit
8cc303ef60
@ -197,6 +197,8 @@ class IconButton extends StatelessWidget {
|
||||
this.splashColor,
|
||||
this.disabledColor,
|
||||
required this.onPressed,
|
||||
this.onHover,
|
||||
this.onLongPress,
|
||||
this.mouseCursor,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
@ -228,6 +230,8 @@ class IconButton extends StatelessWidget {
|
||||
this.splashColor,
|
||||
this.disabledColor,
|
||||
required this.onPressed,
|
||||
this.onHover,
|
||||
this.onLongPress,
|
||||
this.mouseCursor,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
@ -261,6 +265,8 @@ class IconButton extends StatelessWidget {
|
||||
this.splashColor,
|
||||
this.disabledColor,
|
||||
required this.onPressed,
|
||||
this.onHover,
|
||||
this.onLongPress,
|
||||
this.mouseCursor,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
@ -293,6 +299,8 @@ class IconButton extends StatelessWidget {
|
||||
this.splashColor,
|
||||
this.disabledColor,
|
||||
required this.onPressed,
|
||||
this.onHover,
|
||||
this.onLongPress,
|
||||
this.mouseCursor,
|
||||
this.focusNode,
|
||||
this.autofocus = false,
|
||||
@ -478,6 +486,14 @@ class IconButton extends StatelessWidget {
|
||||
/// If this is set to null, the button will be disabled.
|
||||
final VoidCallback? onPressed;
|
||||
|
||||
/// The callback that is called when the button is hovered.
|
||||
final ValueChanged<bool>? onHover;
|
||||
|
||||
/// The callback that is called when the button is long-pressed.
|
||||
///
|
||||
/// If onPressed is set to null, the onLongPress callback is not called.
|
||||
final VoidCallback? onLongPress;
|
||||
|
||||
/// {@macro flutter.material.RawMaterialButton.mouseCursor}
|
||||
///
|
||||
/// If set to null, will default to
|
||||
@ -721,6 +737,8 @@ class IconButton extends StatelessWidget {
|
||||
return _SelectableIconButton(
|
||||
style: adjustedStyle,
|
||||
onPressed: onPressed,
|
||||
onHover: onHover,
|
||||
onLongPress: onPressed != null ? onLongPress : null,
|
||||
autofocus: autofocus,
|
||||
focusNode: focusNode,
|
||||
isSelected: isSelected,
|
||||
@ -774,6 +792,8 @@ class IconButton extends StatelessWidget {
|
||||
autofocus: autofocus,
|
||||
canRequestFocus: onPressed != null,
|
||||
onTap: onPressed,
|
||||
onHover: onHover,
|
||||
onLongPress: onPressed != null ? onLongPress : null,
|
||||
mouseCursor:
|
||||
mouseCursor ?? (onPressed == null ? SystemMouseCursors.basic : SystemMouseCursors.click),
|
||||
enableFeedback: effectiveEnableFeedback,
|
||||
@ -804,6 +824,10 @@ class IconButton extends StatelessWidget {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(StringProperty('tooltip', tooltip, defaultValue: null, quoted: false));
|
||||
properties.add(ObjectFlagProperty<VoidCallback>('onPressed', onPressed, ifNull: 'disabled'));
|
||||
properties.add(ObjectFlagProperty<ValueChanged<bool>>('onHover', onHover, ifNull: 'disabled'));
|
||||
properties.add(
|
||||
ObjectFlagProperty<VoidCallback>('onLongPress', onLongPress, ifNull: 'disabled'),
|
||||
);
|
||||
properties.add(ColorProperty('color', color, defaultValue: null));
|
||||
properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null));
|
||||
properties.add(ColorProperty('focusColor', focusColor, defaultValue: null));
|
||||
@ -820,6 +844,8 @@ class _SelectableIconButton extends StatefulWidget {
|
||||
this.isSelected,
|
||||
this.style,
|
||||
this.focusNode,
|
||||
this.onLongPress,
|
||||
this.onHover,
|
||||
required this.variant,
|
||||
required this.autofocus,
|
||||
required this.onPressed,
|
||||
@ -835,6 +861,8 @@ class _SelectableIconButton extends StatefulWidget {
|
||||
final VoidCallback? onPressed;
|
||||
final String? tooltip;
|
||||
final Widget child;
|
||||
final VoidCallback? onLongPress;
|
||||
final ValueChanged<bool>? onHover;
|
||||
|
||||
@override
|
||||
State<_SelectableIconButton> createState() => _SelectableIconButtonState();
|
||||
@ -879,6 +907,8 @@ class _SelectableIconButtonState extends State<_SelectableIconButton> {
|
||||
autofocus: widget.autofocus,
|
||||
focusNode: widget.focusNode,
|
||||
onPressed: widget.onPressed,
|
||||
onHover: widget.onHover,
|
||||
onLongPress: widget.onPressed != null ? widget.onLongPress : null,
|
||||
variant: widget.variant,
|
||||
toggleable: toggleable,
|
||||
tooltip: widget.tooltip,
|
||||
@ -898,13 +928,15 @@ class _IconButtonM3 extends ButtonStyleButton {
|
||||
required super.onPressed,
|
||||
super.style,
|
||||
super.focusNode,
|
||||
super.onHover,
|
||||
super.onLongPress,
|
||||
super.autofocus = false,
|
||||
super.statesController,
|
||||
required this.variant,
|
||||
required this.toggleable,
|
||||
super.tooltip,
|
||||
required Widget super.child,
|
||||
}) : super(onLongPress: null, onHover: null, onFocusChange: null, clipBehavior: Clip.none);
|
||||
}) : super(onFocusChange: null, clipBehavior: Clip.none);
|
||||
|
||||
final _IconButtonVariant variant;
|
||||
final bool toggleable;
|
||||
|
@ -3017,6 +3017,350 @@ void main() {
|
||||
..rect(color: const Color(0xFF00FF00)), // IconButton overlay.
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Material3 - IconButton variants hovered & onLongPressed', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
late bool onHovered;
|
||||
bool onLongPressed = false;
|
||||
|
||||
void onLongPress() {
|
||||
onLongPressed = true;
|
||||
}
|
||||
|
||||
void onHover(bool hover) {
|
||||
onHovered = hover;
|
||||
}
|
||||
|
||||
// IconButton
|
||||
await tester.pumpWidget(buildAllVariants(onLongPress: onLongPress, onHover: onHover));
|
||||
|
||||
final Finder iconButton = find.widgetWithIcon(IconButton, Icons.favorite);
|
||||
final Offset iconButtonOffset = tester.getCenter(iconButton);
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
|
||||
await gesture.moveTo(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(enabled: false, onLongPress: onLongPress, onHover: onHover),
|
||||
);
|
||||
await gesture.moveTo(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
|
||||
await gesture.removePointer();
|
||||
|
||||
// IconButton.filled
|
||||
await tester.pumpWidget(buildAllVariants(onLongPress: onLongPress, onHover: onHover));
|
||||
|
||||
final Finder iconButtonFilled = find.widgetWithIcon(IconButton, Icons.add);
|
||||
final Offset iconButtonFilledOffset = tester.getCenter(iconButtonFilled);
|
||||
|
||||
await gesture.moveTo(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(enabled: false, onLongPress: onLongPress, onHover: onHover),
|
||||
);
|
||||
await gesture.moveTo(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
|
||||
await gesture.removePointer();
|
||||
|
||||
// IconButton.filledTonal
|
||||
await tester.pumpWidget(buildAllVariants(onLongPress: onLongPress, onHover: onHover));
|
||||
|
||||
final Finder iconButtonFilledTonal = find.widgetWithIcon(IconButton, Icons.add);
|
||||
final Offset iconButtonFilledTonalOffset = tester.getCenter(iconButtonFilledTonal);
|
||||
|
||||
await gesture.moveTo(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(enabled: false, onLongPress: onLongPress, onHover: onHover),
|
||||
);
|
||||
await gesture.moveTo(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
|
||||
await gesture.removePointer();
|
||||
|
||||
// IconButton.outlined
|
||||
await tester.pumpWidget(buildAllVariants(onLongPress: onLongPress, onHover: onHover));
|
||||
|
||||
final Finder iconButtonOutlined = find.widgetWithIcon(IconButton, Icons.add);
|
||||
final Offset iconButtonOutlinedOffset = tester.getCenter(iconButtonOutlined);
|
||||
|
||||
await gesture.moveTo(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(enabled: false, onLongPress: onLongPress, onHover: onHover),
|
||||
);
|
||||
await gesture.moveTo(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
});
|
||||
|
||||
testWidgets('Material2 - IconButton variants hovered & onLongPressed', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
late bool onHovered;
|
||||
bool onLongPressed = false;
|
||||
|
||||
void onLongPress() {
|
||||
onLongPressed = true;
|
||||
}
|
||||
|
||||
void onHover(bool hover) {
|
||||
onHovered = hover;
|
||||
}
|
||||
|
||||
// IconButton
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(onLongPress: onLongPress, onHover: onHover, useMaterial3: false),
|
||||
);
|
||||
|
||||
final Finder iconButton = find.widgetWithIcon(IconButton, Icons.favorite);
|
||||
final Offset iconButtonOffset = tester.getCenter(iconButton);
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
|
||||
await gesture.moveTo(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(
|
||||
enabled: false,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
useMaterial3: false,
|
||||
),
|
||||
);
|
||||
await gesture.moveTo(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
|
||||
await gesture.removePointer();
|
||||
|
||||
// IconButton.filled
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(onLongPress: onLongPress, onHover: onHover, useMaterial3: false),
|
||||
);
|
||||
|
||||
final Finder iconButtonFilled = find.widgetWithIcon(IconButton, Icons.add);
|
||||
final Offset iconButtonFilledOffset = tester.getCenter(iconButtonFilled);
|
||||
|
||||
await gesture.moveTo(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(
|
||||
enabled: false,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
useMaterial3: false,
|
||||
),
|
||||
);
|
||||
await gesture.moveTo(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
|
||||
await gesture.removePointer();
|
||||
|
||||
// IconButton.filledTonal
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(onLongPress: onLongPress, onHover: onHover, useMaterial3: false),
|
||||
);
|
||||
|
||||
final Finder iconButtonFilledTonal = find.widgetWithIcon(IconButton, Icons.add);
|
||||
final Offset iconButtonFilledTonalOffset = tester.getCenter(iconButtonFilledTonal);
|
||||
|
||||
await gesture.moveTo(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(
|
||||
enabled: false,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
useMaterial3: false,
|
||||
),
|
||||
);
|
||||
await gesture.moveTo(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonFilledTonalOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
|
||||
await gesture.removePointer();
|
||||
|
||||
// IconButton.outlined
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(onLongPress: onLongPress, onHover: onHover, useMaterial3: false),
|
||||
);
|
||||
|
||||
final Finder iconButtonOutlined = find.widgetWithIcon(IconButton, Icons.add);
|
||||
final Offset iconButtonOutlinedOffset = tester.getCenter(iconButtonOutlined);
|
||||
|
||||
await gesture.moveTo(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, true);
|
||||
|
||||
await tester.longPressAt(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, true);
|
||||
|
||||
onHovered = false;
|
||||
onLongPressed = false;
|
||||
|
||||
await tester.pumpWidget(
|
||||
buildAllVariants(
|
||||
enabled: false,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
useMaterial3: false,
|
||||
),
|
||||
);
|
||||
await gesture.moveTo(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onHovered, false);
|
||||
|
||||
await tester.longPressAt(iconButtonOutlinedOffset);
|
||||
await tester.pump();
|
||||
expect(onLongPressed, false);
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildAllVariants({
|
||||
bool enabled = true,
|
||||
bool useMaterial3 = true,
|
||||
void Function(bool)? onHover,
|
||||
VoidCallback? onLongPress,
|
||||
}) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(useMaterial3: useMaterial3),
|
||||
home: Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.favorite),
|
||||
onPressed: enabled ? () {} : null,
|
||||
onHover: onHover,
|
||||
onLongPress: onLongPress,
|
||||
),
|
||||
IconButton.filled(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: enabled ? () {} : null,
|
||||
onHover: onHover,
|
||||
onLongPress: onLongPress,
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: enabled ? () {} : null,
|
||||
onHover: onHover,
|
||||
onLongPress: onLongPress,
|
||||
),
|
||||
IconButton.outlined(
|
||||
icon: const Icon(Icons.home),
|
||||
onPressed: enabled ? () {} : null,
|
||||
onHover: onHover,
|
||||
onLongPress: onLongPress,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget wrap({required Widget child, required bool useMaterial3}) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user