Flutter:OnTap not working when Listview is scrolling - flutter

I tried to create a marquee Widget, I using the Listview implement auto-scrolling, but if I added a click event to the text when listview is scrolling, it not clickable.I know it should be a rolling problem, but I don't know how to solve itHere's the code is Marquee widget.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class MarqueeContinuous extends StatefulWidget {
final Widget child;
final Duration duration;
final double stepOffset;
MarqueeContinuous(
{Key key,
this.child,
this.duration = const Duration(seconds: 3),
this.stepOffset = 50.0})
: super(key: key);
#override
_MarqueeContinuousState createState() => _MarqueeContinuousState();
}
class _MarqueeContinuousState extends State<MarqueeContinuous> {
ScrollController _controller;
Timer _timer;
double _offset = 0.0;
#override
void initState() {
super.initState();
_controller = ScrollController(initialScrollOffset: _offset);
_timer = Timer.periodic(widget.duration, (timer) {
double newOffset = _controller.offset + widget.stepOffset;
if (newOffset != _offset) {
_offset = newOffset;
_controller.animateTo(_offset,
duration: widget.duration, curve: Curves.linear);
}
});
}
#override
void dispose() {
_timer.cancel();
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: _controller,
addAutomaticKeepAlives: false,
itemBuilder: (context, index) {
return widget.child;
});
}
}

Remove shrinkWrap and NeverScrollableScrollPhysics for the ListView or any Scrollable widgets that you want to add on-tapped widget to it .

Issue persists in flutter even in marquee package on tap doesn't work as intended.

Related

Flutter How can I make this animation "smoother"

How can I make this animation "smoother" (currently is restarts harshly on the 5 second refresh). I'd like for it to reverse maybe from the last angle?
I call this widget as this:
SpinnerAnimation(body: Icon(FontAwesomeIcons.earthAmericas, size: 33, color: Colors.white,),),
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
class SpinnerAnimation extends StatefulWidget {
late final Widget body;
SpinnerAnimation({required this.body});
#override
_SpinnerAnimationState createState() =>
_SpinnerAnimationState(body: this.body);
}
class _SpinnerAnimationState extends State<SpinnerAnimation>
with SingleTickerProviderStateMixin {
late final Widget body;
_SpinnerAnimationState({required this.body});
late AnimationController _controller;
double change_rotation_speed = 1.2;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 10),
vsync: this,
)..repeat();
//timer repeat every 5 seconds:
Timer.periodic(Duration(seconds: 5), (Timer t) => change_rotation_speed = randoDoubleGeneratorWithRange(1.2, 5) );
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: body,
builder: (BuildContext context, Widget? child) {
return Transform.rotate(
angle: _controller.value * change_rotation_speed * math.pi,
child: child,
);
},
);
}
double randoDoubleGeneratorWithRange(double min, double max) {
//return a random double value:
var random = new Random();
double result = min + random.nextDouble() * (max - min);
//print(result);
return result;
}
}
You can use RotationTransition and with Animation
.............
late AnimationController _controller;
late Animation<double> animationRotation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat(revere:true); // set true for reverse animation
animationRotation =
Tween<double>(begin: 0.0, end: 1.0).animate(_controller);
}
........
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
child: body,
builder: (BuildContext context, Widget? child) {
return RotationTransition(
turns: animationRotation,
child: child,
);
},
);
here demo link on Dartpad: https://dartpad.dev/?id=e316af128311c0b42d7aef2fd9e037df

Flutter | How to know if widget is on top of navigation stack (visible)

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');
}
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.

I'm getting ScrollController attached to multiple scroll views

I have a pageview inside it a custom widget in which I'm passing the scrollController after initialising. And also have some function to manipulate scrollController from that widget but whenever it reaches to run manipulation part it gives me this error.
Unhandled Exception: 'package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 109 pos 12: '_positions.length == 1': ScrollController attached to multiple scroll views.
code
class WeekView<T> extends StatefulWidget {
#override
WeekViewState<T> createState() => WeekViewState<T>();
}
class WeekViewState<T> extends State<WeekView<T>> {
late ScrollController _scrollController;
late PageController _pageController;
#override
void initState() {
super.initState();
_pageController = PageController(initialPage: _currentIndex);
_scrollController = ScrollController();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.enableScrollToEvent) {
if (widget.scrollToEvent == ScrollToEvent.currentTime &&
_controller.events.last.endTime != null) {
_controller.addListener(() {
scrollToCurrentTime(_controller.events.last.endTime!);
});
} else {
_controller.addListener(scrollToEvent);
}
}
}
#override
Widget build(BuildContext context) {
return PageView.builder(
itemCount: _totalWeeks,
controller: _pageController,
onPageChanged: _onPageChange,
itemBuilder: (_, index) { return InternalWeekViewPage<T>(
scrollController: _scrollController,
);
},
),
}
the function,
void scrollToEvent() {
if (_pageController.hasClients) {
_pageController
.animateToPage(
_pageController.initialPage +
((_controller.events.last.date.getDayDifference(DateTime.now())) /
7)
.floor(),
curve: widget.pageTransitionCurve,
duration: widget.pageTransitionDuration,
)
.then((value) {
if (_scrollController.hasClients) {//<<<<<<<<<<< scrollController
if (_controller.events.last.endTime != null) {
_scrollController.animateTo(
math.max(
_controller.events.last.endTime!.hour * _hourHeight -
_scrollController.position.viewportDimension +
_hourHeight,
0),
duration: widget.scrollTransitionDuration,
curve: widget.scrollToEventCurve,
);
}
}
});
}
}
the custom widget,
class InternalWeekViewPage<T> extends StatelessWidget {
const InternalWeekViewPage({
required this.scrollController,
});
#override
Widget build(BuildContext context) {
return Expanded(
child: SingleChildScrollView(
controller: scrollController,
child:(...)
),);
}
}
Note-: I have removed some unnecessary part for more readability
when I scroll manually and then I do the manipulation then it doesn't give me any error but doing it directly give me error.
so why I'm getting this error even though I'm using it only in one place can anyone help me
From this code
itemBuilder: (_, index) {
return InternalWeekViewPage<T>(
scrollController: _scrollController,
);
Item builder returning List of InternalWeekViewPage and having the same _scrollController.
Instead of passing _scrollController, make InternalWeekViewPage statefullWidgte and create and initialize on initState.
Does it solve your issue?

Flutter scroll slowly to the bottom of a dynamic Listview

I have a list view that its item may vary each time. I want to make auto scroll for it.
I tried this code
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(seconds: 10),
curve: Curves.easeOut);
It works perfectly for small list view but when the list view items are 100 or more it start moving so fast.
I also tried to make the duration longer when the list view have more items but it mess up
The issue was caused from the animation itself not the duration.
I solved it by increasing the duration and setting
curve: Curves.linear.
// Declaring the controller and the item size
ScrollController _scrollController;
final itemSize = 100.0;
// Initializing
#override
void initState() {
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
super.initState();
}
// Your list widget (must not be nested lists)
ListView.builder(
controller: _scrollController,
itemCount: <Your list length>,
itemExtent: itemSize,
itemBuilder: (context, index) {
return ListTile(<your items>);
},
),
// With the listener and the itemSize, you can calculate which item
// is on screen using the provided callBack. Something like this:
void _scrollListener() {
setState(() {
var index = (_scrollController.offset / itemSize).round() + 1;
});
}
import 'dart:async';
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final _controller = ScrollController();
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Timer(
Duration(seconds: 2),
() => _controller.animateTo(
_controller.position.maxScrollExtent,
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
),
);
return Scaffold(
body: ListView.builder(itemBuilder:(context, index) {
return Text('hi');
},
controller: _controller,
)
);
}
}

Flutter simple marquee widget and getting ScrollController not attached to any scroll views error

this below simple widget is used in my application for making simple marquee text into another widgets for Text, in that when text length is too short i get this error:
ScrollController not attached to any scroll views error
my problem is know how can i calculate text length in dynamic with and avoid throwing this error?
for example:
MarqueeWidget(
direction: Axis.horizontal,
child: Text(
'$parsedString', //-> text length is not known
style: AppTheme.of(context).caption(
),
),
and this is MarqueeWidget which i used from it
class MarqueeWidget extends StatefulWidget {
final Widget child;
final Axis direction;
final Duration animationDuration, backDuration, pauseDuration;
MarqueeWidget({
#required this.child,
this.direction: Axis.horizontal,
this.animationDuration: const Duration(milliseconds: 7000),
this.backDuration: const Duration(milliseconds: 2000),
this.pauseDuration: const Duration(milliseconds: 2000),
});
#override
_MarqueeWidgetState createState() => _MarqueeWidgetState();
}
class _MarqueeWidgetState extends State<MarqueeWidget> {
ScrollController scrollController = ScrollController();
#override
void initState() {
try{
scroll();
}catch(error){
}
super.initState();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: widget.child,
scrollDirection: widget.direction,
controller: scrollController,
);
}
void scroll() async {
while (true) {
await Future.delayed(widget.pauseDuration);
await scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: widget.animationDuration,
curve: Curves.easeIn);
await Future.delayed(widget.pauseDuration);
await scrollController.animateTo(0.0,
duration: widget.backDuration, curve: Curves.easeOut);
}
}
}
Just change your code as follow,
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
try {
scroll();
} catch (error){
// log error
}
});
}
WidgetsBinding.instance.addPostFrameCallback() method gets called once the build() method gets rendered.
The error in your case is you are calling scroll() method which tries to scroll using scrollController which might be called before the build method gets executed.