Reputation: 582
In flutter, I have a GestureDetector-wrapped Container with a Text child.
When the button is tapped, the shadow will compress, creating a push-down animation effect.
My Code:
class _ActionButtonState extends State<ActionButton> {
bool isPressed = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
// hitTestBehavior: HitTestBehavior.translucent,
// behavior: HitTestBehavior.translucent,
onTapDown: (e) {
setState(() {
isPressed = true;
});
},
onTapCancel: () => setState(() {
isPressed = false;
}),
onTapUp: (e) {
setState(() {
isPressed = false;
});
},
child: AnimatedContainer(
duration: Duration(milliseconds: 100),
padding: EdgeInsets.only(bottom: isPressed ? 0 : 5),
decoration: BoxDecoration(
color: AppColor.btnShadowColor,
borderRadius: BorderRadius.circular(widget.radius),
),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 8),
decoration: BoxDecoration(
color: AppColor.btnColor,
borderRadius: BorderRadius.circular(widget.radius),
),
child: Text("Start", style: TextStyle(
color: AppColor.whiteText,
),),
),
),
);
}
}
The issue arises when the pointer moves to the Text widget, causing GestureDetector to trigger onTapCancel. As a result, the button reverts to its original state.
In my opinon, mouse is not released, and is not leaving the Container. So onTapCancel should not be triggered.
I tried many ways of searching the answer, but I got nothing. I expect someone can help on prevent onTapCancel from being triggered by the child element.
Upvotes: 1
Views: 78
Reputation: 402
Searching the Flutter's github issues, i found this https://github.com/flutter/flutter/issues/19783#issuecomment-654052524
Replace the GestureDetector with Listener widget
class ActionButton extends StatefulWidget {
const ActionButton({super.key});
@override
State<ActionButton> createState() => _ActionButtonState();
}
class _ActionButtonState extends State<ActionButton> {
bool isPressed = false;
@override
Widget build(BuildContext context) {
return Listener(
onPointerDown: (e) {
setState(() {
isPressed = true;
});
},
onPointerCancel: (_) => setState(() {
isPressed = false;
log("tapCancel");
}),
onPointerUp: (e) {
setState(() {
isPressed = false;
log("tapUp");
});
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
padding: EdgeInsets.only(bottom: isPressed ? 0 : 5),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(8),
),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 8),
decoration: BoxDecoration(
color: Colors.lightGreen,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
"Start",
style: TextStyle(color: Colors.white),
),
),
),
);
}
}
Apparently, onPointerUp
is still called even when pointer is outside the widget. This is hacky, but i think adding a position check does the job;
bool isOutside(BuildContext context, Offset postion) {
return postion.dx < 0 ||
postion.dy < 0 ||
postion.dx > (context.size?.width ?? 0) ||
postion.dy > (context.size?.height ?? 0);
}
onPointerUp: (e) {
setState(() {
isPressed = false;
});
if (isOutside(context, e.localPosition)) {
return;
}
// Do something when pointer is inside Listener here
log("hello");
},
Upvotes: 2