How to automatically scroll through all the ListTiles in the Listview.seperated in Flutter?
Here is a solution assuming that all your items in the ListView
have the same itemExtent
.
In this solution, I highlight the current Item as selected. You could also want to stop autoscrolling as soon as you reach the bottom of the list.
Full source code
import 'dart:async';
import 'package:faker/faker.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part '66455867.auto_scroll.freezed.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
Future<List<News>> _fetchNews() async => dummyData;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('News')),
body: FutureBuilder(
future: _fetchNews(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return NewsList(newsList: snapshot.data);
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
} else {
return Center(child: CircularProgressIndicator());
}
},
),
);
}
}
class NewsList extends StatefulWidget {
final List<News> newsList;
const NewsList({
Key key,
this.newsList,
}) : super(key: key);
@override
_NewsListState createState() => _NewsListState();
}
class _NewsListState extends State<NewsList> {
ScrollController _scrollController = ScrollController();
Timer _timer;
double _itemExtent = 100.0;
Duration _scrollDuration = Duration(milliseconds: 300);
Curve _scrollCurve = Curves.easeInOut;
int _autoScrollIncrement = 1;
int _currentScrollIndex = 0;
@override
void initState() {
super.initState();
_timer = Timer.periodic(Duration(seconds: 2), (_) async {
_autoScrollIncrement = _currentScrollIndex == 0
? 1
: _currentScrollIndex == widget.newsList.length - 1
? -1
: _autoScrollIncrement;
_currentScrollIndex += _autoScrollIncrement;
_animateToIndex(_currentScrollIndex);
setState(() {});
});
}
void _animateToIndex(int index) {
_scrollController.animateTo(
index * _itemExtent,
duration: _scrollDuration,
curve: _scrollCurve,
);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ListView(
controller: _scrollController,
itemExtent: _itemExtent,
children: widget.newsList
.map((news) => ListTile(
title: Text(news.title),
subtitle: Text(
news.description,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
selected: widget.newsList[_currentScrollIndex].id == news.id,
selectedTileColor: Colors.amber.shade100,
))
.toList(),
);
}
}
@freezed
abstract class News with _$News {
const factory News({int id, String title, String description}) = _News;
}
final faker = Faker();
final dummyData = List.generate(
10,
(index) => News(
id: faker.randomGenerator.integer(99999999),
title: faker.sport.name(),
description: faker.lorem.sentence(),
),
);
Packages used in the solution:
-
freeze for the
News
Domain Class - build_runner to generate the freezed code
- faker to generate the list of random news
UPDATE : Scroll only once
To stop the autoscrolling at the bottom of the listview, you just need to modify the initState
method:
int _currentScrollIndex;
News _selectedNews;
@override
void initState() {
super.initState();
_currentScrollIndex = -1;
_timer = Timer.periodic(Duration(seconds: 2), (_) async {
setState(() {
if (_currentScrollIndex == widget.newsList.length - 1) {
_timer.cancel();
_selectedNews = null;
} else {
_selectedNews = widget.newsList[++_currentScrollIndex];
_animateToIndex(_currentScrollIndex);
}
});
});
}
We don't need the scroll direction defined as _autoScrollIncrement
. However, I would introduce a new _selectedNews
to easily unselect the last News item when we arrive at the bottom of the list. The selected
flag of our ListTile
would then become:
@override
Widget build(BuildContext context) {
return ListView(
[...]
children: widget.newsList
.map((news) => ListTile(
[...]
selected: _selectedNews?.id == news.id,
[...]
))
.toList(),
);
}
Comments
-
Thejani Saranaguptha over 1 year
Scroll automatically (without any user interaction) through all the ListTiles in the Listview using a Timer in flutter. The below method makes only one ListTile to animate but I want to animate all the ListTiles from top to bottom one by one and again from bottom to top one by one.
The below is the Listview:
@override Widget build(BuildContext context) { return Scaffold( body: Container( child: FutureBuilder( future: fetchNews(), builder: (context, snap) { if (snap.hasData) { news = snap.data; return ListView.separated( //controller: _controller, scrollDirection: scrollDirection, controller: controller, itemBuilder: (context, i) { final NewsModel _item = news[i]; return AutoScrollTag( key: ValueKey(i), controller: controller, index: i, child: ListTile( title: Text('${_item.title}'), subtitle: Text( '${_item.description}', // maxLines: 1, //overflow: TextOverflow.ellipsis, ), ), ); }, separatorBuilder: (context, i) => Divider(), itemCount: news.length, ); } else if (snap.hasError) { return Center( child: Text(snap.error.toString()), ); } else { return Center( child: CircularProgressIndicator(), ); } }, ), ), ); } }
This is the automatic scrolling i have tried:
@override void initState() { super.initState(); timer = Timer.periodic(Duration(seconds: 2), (Timer t) async { await controller.scrollToIndex(1, preferPosition: AutoScrollPosition.begin); });
-
Thejani Saranaguptha over 3 yearsEverything turns to blue text and it does not scroll @Thierry
-
Thierry over 3 yearsWeird. Did you install the dependencies and run the build_runner to generate the
News
class withflutter pub run build_runner watch --delete-conflicting-outputs
? -
Thejani Saranaguptha over 3 yearsDo you have any idea how to stop the autoscrolling at the bottom of the listview? @Thierry
-
Thierry over 3 yearsSure, check the last update of this answer.