Как автоматически прокручивать все элементы списка в Listview.seperated в Flutter?

#flutter #listview #dart #widget

#flutter #просмотр списка #dart #виджет

Вопрос:

Автоматически прокручивать (без какого-либо взаимодействия с пользователем) все элементы списка в Listview, используя таймер в flutter. Приведенный ниже метод позволяет анимировать только один ListTile, но я хочу анимировать все ListTile сверху вниз один за другим и снова снизу вверх один за другим.

Ниже приведен список:

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

Пакеты, используемые в растворе:


ОБНОВЛЕНИЕ : Прокрутите только один раз

Чтобы остановить автоматическую прокрутку в нижней части списка, вам просто нужно изменить 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 , чтобы легко отменить выбор последней новости, когда мы окажемся в нижней части списка. selected Флаг наш ListTile тогда стал бы:

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

Комментарии:

1. Все превращается в синий текст и не прокручивается @Thierry

2. Странно. Вы установили зависимости и запустили build_runner для создания News класса flutter pub run build_runner watch --delete-conflicting-outputs ?

3. Есть ли у вас какие-либо идеи, как остановить автоматическую прокрутку в нижней части listview? @Thierry

4. Конечно, проверьте последнее обновление этого ответа.