Unable to reliably trigger animation with longPress on GestureDetector
You are using a Text
widget to receive the hit within the
GestureDetector
, which have a small hit box compare to the thumb. This might be the reason why you might misclick the hit box occasionally.
You can use the debugPaintPointersEnabled
to see the behavior more clearly (need to do a Hot Restart if the app is running):
import 'package:flutter/rendering.dart';
void main() {
// Add the config here
debugPaintPointersEnabled = true;
runApp(App());
}
You can see that the hit box does not flash all the time, even when we think we hit the Text. To increase the accuracy, let's wrap a Container with size around the Text
GestureDetector(
// ... other lines
child: Container(
width: 100,
height: 50,
color: Colors.blue,
alignment: Alignment.center,
child:
Text('Value: ${_animationController.value.toStringAsFixed(2)}')),
);
You can see that the hit box flashes everytime now
robinwkurtz
Updated on December 27, 2022Comments
-
robinwkurtz over 1 year
I'm trying to create a button with a progress indicator (
CircularProgressIndicator
)Desired flow:
- The user taps on the button it should fire a function
- The user presses the button (and holds), it should trigger the animation and fire a function
- When the user releases their hold, it should reset the animation and fire a function
At this point, my code works on the second time pressing (and holding) the element. The first time around, the animation controller's addListener prints 2-3 times and then stops, whereas the second time, it holds true and continues to print as the user holds the element. Ontap functionality works regardless.
It's happening while running locally on an android and an ios device
Stripped code block:
import 'package:flutter/material.dart'; import 'package:homi_frontend/constants/woopen_colors.dart'; class ProgressButton extends StatefulWidget { ProgressButton({ @required this.onTap, @required this.onLongPress, @required this.onLongPressUp, this.duration = const Duration(seconds: 60), }); final Function onTap; final Function onLongPress; final Function onLongPressUp; final Duration duration; @override ProgressButtonState createState() => ProgressButtonState(); } class ProgressButtonState extends State<ProgressButton> with SingleTickerProviderStateMixin { AnimationController _animationController; bool _beingPressed = false; @override void initState() { _animationController = AnimationController( vsync: this, duration: widget.duration, ); _animationController.addListener(_animationListener); _animationController.addStatusListener(_animationStatusListener); super.initState(); } void _animationListener() { print('Animation Controller Listener'); setState(() {}); } void _animationStatusListener(AnimationStatus status) { print('_animationStatusListener'); if (status == AnimationStatus.completed) { print( 'Completed duration of ${widget.duration}, fire _handleOnLongPressUp'); _handleOnLongPressUp(); } if (status == AnimationStatus.forward) { this.setState(() { _beingPressed = true; }); } } void _handleOnLongPress() { print('_handleOnLongPress'); try { _animationController.forward(); } catch (e) { print('_handleOnLongPress error: ${e.toString()}'); } finally { if (_animationController.status == AnimationStatus.forward) { print('Controller has been started, fire widget.onLongPress'); widget.onLongPress(); } } } void _handleOnLongPressUp() { print('_handleOnLongPressUp'); try { this.setState(() { _beingPressed = false; }); _animationController.reset(); } catch (e) { print('_handleOnLongPressUp error: ${e.toString()}'); } finally { if (_animationController.status == AnimationStatus.dismissed) { print('Controller has been dismissed, fire widget.onLongPressUp'); widget.onLongPressUp(); } } } @override dispose() { _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GestureDetector( key: Key('progressButtonGestureDetector'), behavior: HitTestBehavior.opaque, onLongPress: _handleOnLongPress, onLongPressUp: _handleOnLongPressUp, onTap: widget.onTap, child: Container( width: 80, height: 80, child: Text(_animationController.value.toStringAsFixed(2)), ), ); } }
Output:
flutter: _handleOnLongPress flutter: _animationStatusListener flutter: Controller has been started, fire widget.onLongPress (2) flutter: Animation Controller Listener # here it just seems to loose its connection, but if I press (and hold) again, I get: flutter: _handleOnLongPress flutter: _animationStatusListener flutter: Controller has been started, fire widget.onLongPress (326) flutter: Animation Controller Listener flutter: _handleOnLongPressUp flutter: Animation Controller Listener flutter: _animationStatusListener flutter: Controller has been dismissed, fire widget.onLongPressUp
I've also looked briefly into
RawGestureDetector
but only myTapGestureRecognizer
gestures seem to fire, theLongPressGestureRecognizer
ones don't... even ifTapGestureRecognizer
s are removed._customGestures = Map<Type, GestureRecognizerFactory>(); _customGestures[TapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>( () => TapGestureRecognizer(debugOwner: this), (TapGestureRecognizer instance) { instance ..onTapDown = (TapDownDetails details) { print('onTapDown'); } ..onTapUp = (TapUpDetails details) { print('onTapUp'); } ..onTap = () { print('onTap'); } ..onTapCancel = () { print('onTapCancel'); }; }, ); _customGestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>( () => LongPressGestureRecognizer( duration: widget.duration, debugOwner: this), (LongPressGestureRecognizer instance) { instance ..onLongPress = () { print('onLongPress'); } ..onLongPressStart = (LongPressStartDetails details) { print('onLongPressStart'); _animationController.forward(); } ..onLongPressMoveUpdate = (LongPressMoveUpdateDetails details) { print('onLongPressMoveUpdate'); } ..onLongPressEnd = (LongPressEndDetails details) { print('onLongPressEnd'); _animationController.reset(); } ..onLongPressUp = () { print('onLongPressUp'); }; }, );
Please & thank you for your time!
-
robinwkurtz over 3 yearsThank you @bach in fact, my button was always a
Container
and not just aText
widget, a silly oversight when I stripped down my code for example. I've added in thedebugPaintPointersEnabled
flag, and I can indeed see the initial highlight, though it simply stops after a few miliseconds -
robinwkurtz over 3 yearsIn fact, the
debugPaintPointersEnabled
made me realize I didn't isolate the issue... it appears I have a rerender above my button which is causing the first flash... I don't know why it wouldn't be an issue the second time around but this is a great lead.