4

I am seeking to create a constant scroll of a dynamic number of images across my screen (similar to a news ticker) in Flutter. I want this to be automatic and a constant speed, that also loops.

The simplest solution I have found is to use the Carousel Package which ticks almost all the boxes, except one. I am unable to get a constant scroll speed

A possible solution was to adjust autoPlayInterval to zero, but unfortunately, this paramater appears to need a value of around 50 or greater to run - therefore creating an even scroll.

Any idea on how to tweak it this with this package? Or another suitable solution?

Simplified code:

@override
 Widget build(BuildContext context) {
 return Container(
 child: CarouselSlider(
 items: DynamicImages.list
 .map(
 (e) => Padding(
 padding: const EdgeInsets.all(8.0),
 child: Image.asset('assets/images/$e.png'),
 ),
 )
 .toList(),
 options: CarouselOptions(
 autoPlay: true,
 autoPlayCurve: Curves.linear,
 autoPlayInterval: Duration(milliseconds: 0), /// carousel will not run if set to zero
 autoPlayAnimationDuration: Duration(milliseconds: 1000)
 ),
 ),
 );
 }
}
asked Feb 1, 2022 at 1:15
1

2 Answers 2

6

I found a workaround that still takes advantage of the carousel_slider package. Instead of using autoplay, you just give it a CarouselController.

Start the animation by calling nextPage() in initState. Then in the carousel, set onPageChanged to call nextPage() again, which will continuously scroll from page to page with constant speed. Set the duration for the two nextPage() calls to whatever you want, as long as it's the same duration.

The only pause I experience is when it reaches the end of the list, where it needs to loop back to the first image. But it's negligible enough for my use case.

class _WelcomeScreenState extends State<WelcomeScreen> {
 List<String> _images = [
 "assets/image.jpg",
 // ...
 ];
 final CarouselController _carouselController = CarouselController();
 @override
 void initState() {
 super.initState();
 
 // Add this to start the animation
 WidgetsBinding.instance.addPostFrameCallback((_) {
 _carouselController.nextPage(duration: Duration(milliseconds: 2000));
 });
 }
 @override
 Widget build(BuildContext context) {
 return WillPopScope(
 onWillPop: () async => false,
 child: ScreenBackgroundContainer(
 child: Scaffold(
 body: SafeArea(
 child: Container(
 height: double.maxFinite,
 width: double.maxFinite,
 child: SingleChildScrollView(
 physics: BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
 padding: const EdgeInsets.symmetric(vertical: 16.0),
 child: Column(
 children: [
 // Carousel
 CarouselSlider(
 carouselController: _performerCarouselController,
 options: CarouselOptions(
 pageSnapping: false,
 height: 146,
 viewportFraction: 114 / MediaQuery.of(context).size.width,
 // Add this
 onPageChanged: (_, __) {
 _performerCarouselController.nextPage(duration: Duration(milliseconds: 2000));
 }),
 items: _performerImages
 .map((image) => ClipRRect(
 borderRadius: BorderRadius.circular(16.0),
 child: Image.asset(image, width: 90, fit: BoxFit.cover)))
 .toList(),
 ),
 ],
 ),
 ),
 ),
 ),
 ),
 ),
 );
 }
}

Screenshot

answered Jul 7, 2022 at 18:34
Sign up to request clarification or add additional context in comments.

1 Comment

It worked! thanks.
1

So, I've been working on this since posting and come up with a solution. I am posting my answer in case it helps any others in the future.

Basically, instead of using Carousel package, I used ListView.builder which then continuously grows as needed.

Of note, I needed to use WidgetsBinding.instance.addPostFrameCallback((timeStamp) {}); to get this to work.

It would still be great to see any other solutions, (as I am sure the below workaround could be improved).

import 'package:flutter/material.dart';
class ScrollLoop extends StatefulWidget {
 const ScrollLoop({Key? key}) : super(key: key);
 @override
 _ScrollLoopState createState() => _ScrollLoopState();
}
class _ScrollLoopState extends State<ScrollLoop> {
 ScrollController _controller = ScrollController();
 /// [_list] is growable and holds the assets which will scroll.
 List<String> _list = [
 "assets/images/image1.png",
 /// etc...
 ];
 /// [_list2] holds duplicate data and is used to append to [_list].
 List<String> _list2 = [];
 /// [_listAppended] ensures [_list] is only appended once per cycle.
 bool _listAppended = false;
 @override
 void initState() {
 _list2.addAll(_list);
 /// To auto-start the animation when the screen loads.
 WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
 _startScroll();
 });
 /// The [_controller] will notify [_list] to be appended when the animation is near completion.
 _controller.addListener(
 () {
 if (_controller.position.pixels >
 _controller.position.maxScrollExtent * 0.90) {
 if (_listAppended == false) {
 _list.addAll(_list2);
 _listAppended = true;
 }
 }
 /// The [_controller] will listen for when the animation cycle completes,
 /// so this can immediately re-start from the completed position.
 if (_controller.position.pixels ==
 _controller.position.maxScrollExtent) {
 _listAppended = false;
 setState(() {});
 WidgetsBinding.instance!.addPostFrameCallback(
 (timeStamp) {
 _startScroll();
 },
 );
 }
 },
 );
 super.initState();
 }
 @override
 void didChangeDependencies() {
 super.didChangeDependencies();
 }
 void _startScroll() {
 _controller.animateTo(_controller.position.maxScrollExtent,
 duration: Duration(milliseconds: 8000), curve: Curves.linear);
 }
 @override
 void dispose() {
 _controller.dispose();
 super.dispose();
 }
 @override
 Widget build(BuildContext context) {
 final _size = MediaQuery.of(context).size;
 return AbsorbPointer(
 child: Material(
 child: Center(
 child: Column(
 mainAxisAlignment: MainAxisAlignment.center,
 children: [
 Expanded(
 child: ListView.builder(
 shrinkWrap: true,
 controller: _controller,
 scrollDirection: Axis.horizontal,
 itemCount: _list.length,
 itemBuilder: (context, index) {
 return Padding(
 padding: const EdgeInsets.all(8.0),
 child: Container(
 width: _size.width / 4,
 height: _size.height / 10,
 child: Image.asset(_list[index]),
 ),
 );
 },
 ),
 ),
 ],
 ),
 ),
 ),
 );
 }
}

enter image description here

answered Feb 2, 2022 at 7:56

1 Comment

It works, but partially. The first animation goes slow, but after that it accelerates and keeps it constant

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.