如何在 Flutter 中自动滚动 Listview.separated 中的所有 List Tiles?

Posted

技术标签:

【中文标题】如何在 Flutter 中自动滚动 Listview.separated 中的所有 List Tiles?【英文标题】:How to automatically scroll through all the ListTiles in the Listview.seperated in Flutter? 【发布时间】:2021-06-01 23:15:30 【问题描述】:

使用 Flutter 中的 Timer 自动滚动(无需任何用户交互)在 Listview 中的所有 ListTiles。下面的方法只制作一个 ListTile 动画,但我想将所有 ListTiles 从上到下一个一个地动画化,然后从下到上一个一个地动画化。

下面是列表视图:

@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(),
              );
            
          ,
        ),
      ),
    );
  

这是我尝试过的自动滚动:

@override
  void initState() 
    super.initState();
    timer = Timer.periodic(Duration(seconds: 2), (Timer t) async 
        await controller.scrollToIndex(1,
            preferPosition: AutoScrollPosition.begin);
    );

【问题讨论】:

【参考方案1】:

这是一个假设您在ListView 中的所有项目都具有相同itemExtent 的解决方案。

在此解决方案中,我将当前项目突出显示为选中状态。您还可能希望在到达列表底部后立即停止自动滚动。

完整源代码

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(),
  ),
);

解决方案中使用的包:

freeze 用于 News 域类 build_runner生成冻结代码 faker生成随机新闻列表

更新:只滚动一次

要停止listview底部的自动滚动,只需要修改initState方法即可:

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);
      
    );
  );

我们不需要定义为_autoScrollIncrement 的滚动方向。但是,我会引入一个新的_selectedNews,以便在我们到达列表底部时轻松取消选择最后一个新闻项目。 ListTileselected 标志将变为:

@override
Widget build(BuildContext context) 
  return ListView(
    [...]
    children: widget.newsList
      .map((news) => ListTile(
        [...]
        selected: _selectedNews?.id == news.id,
        [...]
      ))
      .toList(),
  );

【讨论】:

一切都变成蓝色文本,而且它不滚动@Thierry 很奇怪。您是否安装了依赖项并运行 build_runner 以使用 flutter pub run build_runner watch --delete-conflicting-outputs 生成 News 类? 你知道如何停止列表视图底部的自动滚动吗? @蒂埃里 好的,请查看此答案的最新更新。

以上是关于如何在 Flutter 中自动滚动 Listview.separated 中的所有 List Tiles?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Flutter 中自动滚动 Listview.separated 中的所有 List Tiles?

如何在 Flutter 中自动滚动到 SingleChildScrollView 内的行的位置

Flutter:如何自动滚动到 SingleChildScrollView 的末尾

Flutter中如何返回刷新上一页?

如何在颤动中自动滚动文本?

输入文本时自动扩展的 Flutter 文本字段,然后在达到特定高度时开始滚动文本