How to add multiple gesture recognizers to TextSpan? - flutter

I'd like to add both a TapGestureRecognizer and a LongPressGestureRecognizer to a TextSpan. Right now I can add either one, but not both.
I've looked into the GestureDetector class and wanted to wrap TextSpan with it, but the RichText element only accepts TextSpans and not Widgets.
This is what I have now:
TextSpan(
text: "some text",
recognizer: TapGestureRecognizer()
..onTap = () { print('tapped'); }
)
And I want to add ..onLongPress somewhere in that code.
At the end both gestures should work on the single text span.

It doesn't seem to be possible to add more than one GestureRecognizer to a TextSpan, but here is a workaround for your case with just the TapGestureRecognizer and using the onTapUp and onTapDown to detect a tap and to simulate a long tap, using a Timer:
TapGestureRecognizer _tapGestureRecognizer;
Timer _timer;
#override
void initState() {
super.initState();
_initRecognizer();
}
_initRecognizer() {
_tapGestureRecognizer = TapGestureRecognizer();
_tapGestureRecognizer.onTapUp = (_) {
if (_timer != null && _timer.isActive) {
print('Tap');
_timer.cancel();
}
};
_tapGestureRecognizer.onTapDown = (_) {
_timer = Timer(Duration(seconds: 1), () {
print('Long Tap');
});
};
}
#override
void dispose() {
if (_timer != null) {
_timer.cancel();
_timer = null;
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
appBar: AppBar(),
body: Padding(
padding: EdgeInsets.all(16.0),
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: "This is some text.",
recognizer: _tapGestureRecognizer,
style: Theme.of(context).textTheme.title,
),
TextSpan(
text:
"Another piece of text. Another piece of text. Another piece of text. Another piece of text.",
style: Theme.of(context).textTheme.title,
),
],
),
),
),
);
}

You can use WidgetSpan to set into your span and detect TapGestureRecognizer and LongPressGestureRecognizer by GestureDetector
TextSpan(
children: <InlineSpan>[
TextSpan(text: 'Flutter is'),
WidgetSpan(
child: GestureDetector(
onTap: () {
},
onLongPress: () {
},
child: Text(' Hello World! '),
)
),
TextSpan(text: 'the best!'),
],
)

I combined TapGestureRecognizer and LongPressGestureRecognizer into a simple TapAndLongPressGestureRecognizer:
import 'package:flutter/gestures.dart';
///
/// A simple [GestureRecognizer] that combines [TapGestureRecognizer] and [LongPressGestureRecognizer]
/// It only supports two simple callbacks [onTap] and [onLongPress]
///
class TapAndLongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
/// Creates a gesture recognizer.
TapAndLongPressGestureRecognizer({
Duration? duration,
double? postAcceptSlopTolerance,
#Deprecated(
'Migrate to supportedDevices. '
'This feature was deprecated after v2.3.0-1.0.pre.',
)
PointerDeviceKind? kind,
Set<PointerDeviceKind>? supportedDevices,
Object? debugOwner,
}) : super(
deadline: duration ?? kLongPressTimeout,
postAcceptSlopTolerance: postAcceptSlopTolerance,
kind: kind,
supportedDevices: supportedDevices,
debugOwner: debugOwner,
);
bool _longPressAccepted = false;
// The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a
// different set of buttons, the gesture is canceled.
int? _initialButtons;
bool _sentTapDown = false;
bool _wonArenaForPrimaryPointer = false;
PointerDownEvent? _down;
PointerUpEvent? _up;
/// Called when a long press gesture by a primary button has been recognized.
///
GestureLongPressCallback? onLongPress;
/// A pointer has stopped contacting the screen, which is recognized as a tap
/// of a primary button.
///
/// This triggers on the up event, if the recognizer wins the arena with it
/// or has previously won, immediately following [onTapUp].
///
GestureTapCallback? onTap;
VelocityTracker? _velocityTracker;
#override
bool isPointerAllowed(PointerDownEvent event) {
switch (event.buttons) {
case kPrimaryButton:
if (onLongPress == null) {
return false;
}
break;
default:
return false;
}
return super.isPointerAllowed(event);
}
#override
void addAllowedPointer(PointerDownEvent event) {
assert(event != null);
if (state == GestureRecognizerState.ready) {
// If there is no result in the previous gesture arena,
// we ignore them and prepare to accept a new pointer.
if (_down != null && _up != null) {
assert(_down!.pointer == _up!.pointer);
_resetTap();
}
assert(_down == null && _up == null);
// `_down` must be assigned in this method instead of `handlePrimaryPointer`,
// because `acceptGesture` might be called before `handlePrimaryPointer`,
// which relies on `_down` to call `handleTapDown`.
_down = event;
}
if (_down != null) {
// This happens when this tap gesture has been rejected while the pointer
// is down (i.e. due to movement), when another allowed pointer is added,
// in which case all pointers are simply ignored. The `_down` being null
// means that _reset() has been called, since it is always set at the
// first allowed down event and will not be cleared except for reset(),
super.addAllowedPointer(event);
}
}
#override
void didExceedDeadline() {
// Exceeding the deadline puts the gesture in the accepted state.
resolve(GestureDisposition.accepted);
_longPressAccepted = true;
super.acceptGesture(primaryPointer!);
_checkLongPressStart();
}
#override
void handlePrimaryPointer(PointerEvent event) {
if (!event.synthesized) {
if (event is PointerDownEvent) {
_velocityTracker = VelocityTracker.withKind(event.kind);
_velocityTracker!.addPosition(event.timeStamp, event.localPosition);
}
if (event is PointerMoveEvent) {
assert(_velocityTracker != null);
_velocityTracker!.addPosition(event.timeStamp, event.localPosition);
}
}
if (event is PointerUpEvent) {
if (_longPressAccepted == true) {
_checkLongPressEnd(event);
_resetTap();
} else {
_up = event;
_checkTapUp();
}
_resetLongPress();
} else if (event is PointerCancelEvent) {
_resetLongPress();
if (_sentTapDown) {
_checkTapCancel(event, '');
}
_resetTap();
} else if (event is PointerDownEvent) {
_initialButtons = event.buttons;
_down = event;
_checkTapDown();
} else if (event is PointerMoveEvent) {
if (event.buttons != _initialButtons) {
resolve(GestureDisposition.rejected);
stopTrackingPointer(primaryPointer!);
}
}
}
void _checkTapUp() {
if (!_wonArenaForPrimaryPointer || _up == null) {
return;
}
assert(_up!.pointer == _down!.pointer);
switch (_down!.buttons) {
case kPrimaryButton:
if (onTap != null) {
invokeCallback<void>('onTap', onTap!);
}
break;
default:
}
_resetTap();
}
void _checkTapDown() {
if (_sentTapDown) {
return;
}
_sentTapDown = true;
}
void _checkTapCancel(PointerCancelEvent? event, String note) {}
void _resetTap() {
_sentTapDown = false;
_wonArenaForPrimaryPointer = false;
_up = null;
_down = null;
}
void _checkLongPressStart() {
switch (_initialButtons) {
case kPrimaryButton:
if (onLongPress != null) {
invokeCallback<void>('onLongPress', onLongPress!);
}
break;
default:
assert(false, 'Unhandled button $_initialButtons');
}
}
void _checkLongPressEnd(PointerEvent event) {
_velocityTracker = null;
}
void _resetLongPress() {
_longPressAccepted = false;
_initialButtons = null;
_velocityTracker = null;
}
#override
void resolve(GestureDisposition disposition) {
if (disposition == GestureDisposition.rejected) {
if (_longPressAccepted) {
// This can happen if the gesture has been canceled. For example when
// the buttons have changed.
_resetLongPress();
}
if (_wonArenaForPrimaryPointer && _sentTapDown) {
// This can happen if the gesture has been canceled. For example, when
// the pointer has exceeded the touch slop, the buttons have been changed,
// or if the recognizer is disposed.
_checkTapCancel(null, 'spontaneous');
_resetTap();
}
}
super.resolve(disposition);
}
#override
void acceptGesture(int pointer) {
if (pointer == primaryPointer && _down != null) {
_checkTapDown();
_wonArenaForPrimaryPointer = true;
_checkTapUp();
}
}
#override
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
if (pointer == primaryPointer) {
// Another gesture won the arena.
assert(state != GestureRecognizerState.possible);
if (_sentTapDown) {
_checkTapCancel(null, 'forced');
}
_resetTap();
}
}
#override
String get debugDescription => 'tap or long press';
}

Related

How to add continuously change color of container in flutter between RGB values

I want to create an animation where the container changes colors just the LED lights do in real life.
I this would be automatic so I dont want any 'onPressed()' working classes
I tried doing it through 'Timer' and 'AnimatedContainer()' but the timer only responds to initState value i.e. 0
Either help me setState the state of Container after every second or give me some another solution.
This is my code currently
late Timer timer;
int start = 0;
void startTimer() {
const oneSec = Duration(seconds: 1);
timer = Timer.periodic(
oneSec,
(Timer timer) {
setState(() {
start++;
});`your text`
},
);
}
#override
void initState() {
super.initState();
startTimer();
setState(() {
_changeColor();
});
}
_changeColor() {
setState(() {
if (start % 3 == 0) {
changingColor = Colors.green;
} else if (start % 3 == 1) {
changingColor = Colors.blue;
} else if (start % 3 == 2) {
changingColor = Colors.red;
}
});
}
Use setState for start won't call _changeColor, so you have to call _changeColor inside Timer.
class LEDWidget extends StatefulWidget {
const LEDWidget({super.key});
#override
State<LEDWidget> createState() => _LEDWidgetState();
}
class _LEDWidgetState extends State<LEDWidget> {
Color changingColor = Colors.green;
int start = 0;
late Timer timer;
#override
void dispose() {
timer.cancel();
super.dispose();
}
#override
void initState() {
super.initState();
timer = Timer.periodic(const Duration(seconds: 1), (_) {
start++;
_changeColor();
});
}
_changeColor() {
setState(() {
if (start % 3 == 0) {
changingColor = Colors.green;
} else if (start % 3 == 1) {
changingColor = Colors.blue;
} else if (start % 3 == 2) {
changingColor = Colors.red;
}
});
}
#override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: const Duration(seconds: 1),
color: changingColor,
);
}
}

internet listen on every screen flutter

I want to check the internet on every page in my flutter app how I can do that without repeating the same code on every screen?
here is my code
bool internet = true;
#override
void initState() {
countryController.text = "+961";
Connectivity().onConnectivityChanged.listen((connectionState) {
if (connectionState == ConnectivityResult.none) {
setState(() {
internet = false;
});
// valueNotifierBook.incrementNotifier();
} else {
setState(() {
internet = true;
});
}
});
super.initState();
}

Flutter NotificationListener crashed app for some seconds

I am using NotificationListener<UserScrollNotification> to change the boolean value on the basis of scroll direction... but when i scroll page for very first time when app starts all the content gets disappear for some seconds...I am using Provider,Please help me... It only happens for the very first time
Here i am trying to change boolean variable
NotificationListener<UserScrollNotification>(
onNotification: (notification) {
final ScrollDirection direction =
notification.direction;
if (direction == ScrollDirection.reverse) {
value.changeVisiblity(true);
} else if (direction ==
ScrollDirection.forward) {
value.changeVisiblity(false);
}
return false;
},
here is my provider class where boolean is defined
bool isVisible = false;
changeVisiblity(bool value) {
isVisible = value;
notifyListeners();
}
here is a video
Video

Flutter Timer.periodic register/run once

i would like to have a Timer runing once in main.dart to check user activity
#override
void initState() {
super.initState();
initPlatformState();
_fetchMasterData("a", "b");
_startActivityTimer();
}
bool _activityTimerRunning = false;
void _startActivityTimer() {
if (!_activityTimerRunning) {
Timer.periodic(Duration(seconds: 5), (timer) {
_timerTicked(timer);
setState(() {
_activityTimerRunning = true;
});
print("Timer started");
});
}
}
But initState gets called more than once, so it is not the right place to register the timer. Where should it be placed?
The code in the question has i silly mistake, the print statement is in the wrong place. Here is the working code:
#override
void initState() {
super.initState();
initPlatformState();
_fetchMasterData("a", "b");
_startActivityTimer();
}
void _startActivityTimer() {
print("Timer started");
Timer.periodic(Duration(seconds: 5), (timer) {
_timerTicked(timer);
});
}
"TimerStarted" get executed only once
Try this sample code.
int start = 5;
fName(){
Timer _timer;
const oneSecond = Duration(seconds: 5);
_timer = Timer.periodic(
oneSecond,
(Timer timer) => setState((){
if(start == 0)
timer.cancel();
else{
_activityTimerRunning = true;
start -= 1;
}
})
);
}
When you want to start you timer, call fName() method, after that timer can started. So and also you can check condition, when
if(start != 0){
...
}

How to notice when user released his/her finger from scrolling

I have a listView.Builder and want to do certain calculation based on position of scrollController when user released his/her finger on the screen?
the calculation part is easy in flutter but How can I notice when user released his finger from scrolling to do some action ?
This worked for me:
class _YourScreenState extends State<YourScreen> {
bool _isDragging = false;
ScrollUpdateNotification _notification;
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollUpdateNotification>(
onNotification: (notification) {
_notification = notification;
if (_hasLiftedFinger) {
// do your thing
}
_setDraggingState();
// confidently use _isDragging here
return false;
},
child: YourListView(),
);
}
void _setDraggingState() {
if (_hasLoweredFinger) {
_isDragging = true;
return;
}
if (_hasLiftedFinger) {
_isDragging = false;
}
}
bool get _hasLoweredFinger => (!_isDragging && _notification.dragDetails != null);
bool get _hasLiftedFinger => (_isDragging && _notification.dragDetails == null);
}
Use NotificationListener Widget. Here is a short clip about it.
The code you may want would look like this:
#override
Widget build(BuildContext context) {
return NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollStartNotification) {
debugPrint('Started');
}
if (notification is ScrollUpdateNotification) {
debugPrint('Updated');
}
if (notification is ScrollEndNotification) {
debugPrint('Ended');
}
return false;
},
child: YourListView(),
);
}