auto_scroll_list_view.dart 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import 'dart:async';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:flutter/rendering.dart';
  4. import 'package:flutter_screenutil/flutter_screenutil.dart';
  5. class AutoScrollListView extends StatefulWidget {
  6. final NullableIndexedWidgetBuilder itemBuilder;
  7. final int itemCount;
  8. final Axis scrollDirection;
  9. final EdgeInsetsGeometry? padding;
  10. final bool isAutoScrolling;
  11. const AutoScrollListView({
  12. super.key,
  13. required this.itemBuilder,
  14. required this.itemCount,
  15. this.padding,
  16. this.isAutoScrolling = true,
  17. this.scrollDirection = Axis.horizontal,
  18. });
  19. @override
  20. State<AutoScrollListView> createState() => _AutoScrollListViewState();
  21. }
  22. class _AutoScrollListViewState extends State<AutoScrollListView> {
  23. final ScrollController scrollController = ScrollController();
  24. Timer? _timer;
  25. bool _isUserScrolling = true;
  26. @override
  27. void initState() {
  28. super.initState();
  29. _isUserScrolling = widget.isAutoScrolling;
  30. _startAutoScroll();
  31. }
  32. @override
  33. void dispose() {
  34. super.dispose();
  35. scrollController.dispose();
  36. _timer?.cancel();
  37. }
  38. void _startAutoScroll() {
  39. _timer = Timer.periodic(Duration(milliseconds: 50), (timer) {
  40. if (!_isUserScrolling || !scrollController.hasClients) return;
  41. final maxScrollExtent = scrollController.position.maxScrollExtent;
  42. final current = scrollController.position.pixels;
  43. double next = current + 1;
  44. if (next >= maxScrollExtent) {
  45. next = 0;
  46. }
  47. scrollController.jumpTo(next);
  48. });
  49. }
  50. void _onUserScroll() {
  51. if (!_isUserScrolling) {
  52. return;
  53. }
  54. _timer?.cancel();
  55. _timer = Timer(Duration(seconds: 1), () {
  56. setState(() {
  57. _isUserScrolling = widget.isAutoScrolling;
  58. });
  59. _startAutoScroll();
  60. });
  61. }
  62. @override
  63. Widget build(BuildContext context) {
  64. return NotificationListener<UserScrollNotification>(
  65. onNotification: (notification) {
  66. if (notification.direction != ScrollDirection.idle) {
  67. _onUserScroll(); // 用户滑动时暂停自动滚动
  68. }
  69. return false; // 不阻止子组件的事件传递
  70. },
  71. child: ListView.builder(
  72. padding: widget.padding,
  73. scrollDirection: widget.scrollDirection,
  74. itemCount: 1000000,
  75. itemBuilder: (context, index) {
  76. return widget.itemBuilder(context, index % widget.itemCount);
  77. },
  78. controller: scrollController,
  79. ),
  80. );
  81. }
  82. }
  83. // 多行横向自动滚动列表
  84. /// 一个支持自动滚动的多行横向列表,每一行自动向右滚动(模拟跑马灯效果)
  85. class StaggeredAutoScrollListView extends StatefulWidget {
  86. /// 子项构建器(需根据 itemIndex 构建对应组件)
  87. final NullableIndexedWidgetBuilder itemBuilder;
  88. /// 实际数据项数量(不是扩展后的数量)
  89. final int itemCount;
  90. /// 每行的内边距
  91. final EdgeInsetsGeometry? padding;
  92. /// 是否启用自动滚动
  93. final bool isAutoScrolling;
  94. const StaggeredAutoScrollListView({
  95. super.key,
  96. required this.itemBuilder,
  97. required this.itemCount,
  98. this.padding,
  99. this.isAutoScrolling = true,
  100. });
  101. @override
  102. State<StaggeredAutoScrollListView> createState() => _StaggeredAutoScrollListViewState();
  103. }
  104. class _StaggeredAutoScrollListViewState extends State<StaggeredAutoScrollListView> {
  105. final int rowCount = 3; // 列表的总行数
  106. final List<ScrollController> _controllers = []; // 每一行的滚动控制器
  107. final List<Timer?> _timers = []; // 每一行对应的自动滚动定时器
  108. final int loopFactor = 3; // 每行内容扩展的倍数,防止滚动过短
  109. @override
  110. void initState() {
  111. super.initState();
  112. for (int i = 0; i < rowCount; i++) {
  113. _controllers.add(ScrollController());
  114. }
  115. if (widget.isAutoScrolling) {
  116. WidgetsBinding.instance.addPostFrameCallback((_) => _startAllScrolls());
  117. }
  118. }
  119. @override
  120. void dispose() {
  121. // 销毁滚动控制器
  122. for (final controller in _controllers) {
  123. controller.dispose();
  124. }
  125. // 取消所有定时器
  126. for (final timer in _timers) {
  127. timer?.cancel();
  128. }
  129. super.dispose();
  130. }
  131. /// 启动所有行的自动滚动
  132. void _startAllScrolls() {
  133. for (int i = 0; i < rowCount; i++) {
  134. final controller = _controllers[i];
  135. _timers.add(Timer.periodic(const Duration(milliseconds: 50), (_) {
  136. if (!controller.hasClients) return;
  137. final maxExtent = controller.position.maxScrollExtent;
  138. final current = controller.position.pixels;
  139. final next = current + 1;
  140. if (next >= maxExtent) {
  141. final viewportWidth = controller.position.viewportDimension;
  142. final contentWidth = maxExtent + viewportWidth;
  143. final originalContentWidth = contentWidth / loopFactor;
  144. final offset = next - maxExtent;
  145. controller.jumpTo(originalContentWidth + offset);
  146. } else {
  147. controller.jumpTo(next);
  148. }
  149. }));
  150. }
  151. }
  152. @override
  153. @override
  154. Widget build(BuildContext context) {
  155. final actualItemsPerRow = (widget.itemCount / rowCount).ceil();
  156. return Column(
  157. children: List.generate(rowCount, (rowIndex) {
  158. final startIndex = rowIndex * actualItemsPerRow;
  159. final endIndex = (startIndex + actualItemsPerRow).clamp(0, widget.itemCount);
  160. final rowItems = List.generate(endIndex - startIndex, (i) => startIndex + i);
  161. final extendedItems = List.generate(
  162. rowItems.length * loopFactor,
  163. (i) => rowItems[i % rowItems.length],
  164. );
  165. return SizedBox(
  166. height: 50.w,
  167. child: ListView.builder(
  168. controller: _controllers[rowIndex],
  169. scrollDirection: Axis.horizontal,
  170. padding: widget.padding,
  171. itemCount: extendedItems.length,
  172. itemBuilder: (context, index) => widget.itemBuilder(context, extendedItems[index]),
  173. ),
  174. );
  175. }),
  176. );
  177. }
  178. }