Reputation: 14656
In my Flutter app, I wish to pause an animation if a new page is pushed to the navigation stack.
Question: From a widgets perspective, is there any way to know if the widget is at the top of the navigation stack?
Example:
while (isTopOfNavigationStack) {
// Do the animation
}
Edit 1: More details
Im using the library widget_marquee for creating a horizontal ticker. This library works great until a new page is pushed to the navigation stack. When this happens, the while-loop
becomes infinite and the application freezes.
library widget_marquee;
import 'dart:developer';
import 'package:flutter/material.dart';
/// Rotates the [child] widget indefinitely along the horizontal axis if the
/// content extends pass the edge of the render area.
///
/// [delayDuration] - One time delay to wait before starting the text rotation
/// [gap] - Spacing to add between widget end and start
/// [loopDuration] - Time for one full rotation of the child
/// [onLoopFinish] - Function to run upon finishing each loop
/// [onScrollingTap]
/// [pixelsPerSecond] - Alternate to loop duration
class Marquee extends StatelessWidget {
const Marquee({
Key? key,
required this.child,
this.delayDuration = const Duration(milliseconds: 1500),
this.gap = 50,
this.loopDuration = const Duration(milliseconds: 8000),
this.onLoopFinish = _onLoopFinish,
this.onScrollingTap = _onScrollingTap,
this.pixelsPerSecond = 0,
}) : super(key: key);
final Widget child;
final Duration delayDuration;
final double gap;
final Duration loopDuration;
final Future<void> Function() onLoopFinish;
final Future<void> Function() onScrollingTap;
final int pixelsPerSecond;
@override
Widget build(BuildContext context) {
return _Marquee(
key: UniqueKey(),
child: child,
delay: delayDuration,
gap: gap,
loopDuration: loopDuration,
onLoopFinish: onLoopFinish,
onScrollingTap: onScrollingTap,
pps: pixelsPerSecond,
);
}
}
class _Marquee extends StatefulWidget {
const _Marquee({
required Key key,
required this.child,
required this.delay,
required this.gap,
required this.loopDuration,
required this.onLoopFinish,
required this.onScrollingTap,
required this.pps,
}) : super(key: key);
final Widget child;
final Duration delay;
final double gap;
final Duration loopDuration;
final Future<void> Function() onLoopFinish;
final Future<void> Function() onScrollingTap;
final int pps;
@override
_MarqueeState createState() => _MarqueeState();
}
class _MarqueeState extends State<_Marquee> with TickerProviderStateMixin {
late double contentArea;
bool isScrolling = false;
late ScrollController scrollController;
List<Widget> widgets = <Widget>[];
@override
void initState() {
super.initState();
}
@override
void didChangeDependencies() {
scrollController = ScrollController(
initialScrollOffset: 0.0,
keepScrollOffset: false,
);
widgets = <Widget>[widget.child];
// Initialize the scroll controller
WidgetsBinding.instance?.addPostFrameCallback(scroll);
super.didChangeDependencies();
}
void scroll(_) async {
if (scrollController.position.maxScrollExtent > 0) {
late Duration duration;
final double initMax = scrollController.position.maxScrollExtent;
// Add a sized box and duplicate widget to the row
setState(() {
widgets.add(SizedBox(width: widget.gap));
widgets.add(widget.child);
});
await Future<dynamic>.delayed(widget.delay);
try {
setState(() {
isScrolling = true;
});
while (scrollController.hasClients) {
// Calculate the position where the duplicate widget lines up with the original
final scrollExtent =
(initMax * 2) - (initMax - contentArea) + widget.gap;
// Set the duration of the animation
if (widget.pps <= 0) {
duration = widget.loopDuration;
} else {
duration = Duration(
// Calculate the duration based on the pixels per second
milliseconds: ((scrollExtent / widget.pps) * 1000).toInt(),
);
}
await scrollController.animateTo(
scrollExtent,
duration: duration,
curve: Curves.linear,
);
// Jump to the beginning of the view to imitate loop
scrollController.jumpTo(0);
await widget.onLoopFinish();
}
} catch (e) {
log('Marquee element has been disposed');
}
}
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
contentArea = constraints.maxWidth;
// Thanks to how widgets work, the gesture detector is only triggered
// if there's nothing clickable in the child
return GestureDetector(
onTap: () async {
if (isScrolling) {
await widget.onScrollingTap();
}
},
child: Container(
alignment: Alignment.center,
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Row(
children: widgets,
),
scrollDirection: Axis.horizontal,
controller: scrollController,
),
),
);
},
);
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
}
Future<void> _onLoopFinish() async {}
Future<void> _onScrollingTap() async {
log('Marquee onScrollingTap function triggered');
}
Upvotes: 7
Views: 10238
Reputation: 23
This is a pretty old question, but I thought the following would work:
if (!Navigator.of(context).canPop())
or
if (!Navigator.of(globalKey.currentContext!).canPop()
Upvotes: 0
Reputation: 356
You can get to know if the current route is the top route in navigation with ModalRoute.of(context)?.isCurrent
. In your example should be something like
final _isTopOfNavigationStack = ModalRoute.of(context)?.isCurrent ?? false;
while (_isTopOfNavigationStack) {
// Do the animation
}
Check this answer if you need more details.
Upvotes: 18