auto_scroll_list_view.dart 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  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. final NullableIndexedWidgetBuilder itemBuilder;
  87. final int itemCount;
  88. final EdgeInsetsGeometry? padding;
  89. final bool isAutoScrolling;
  90. const StaggeredAutoScrollListView({
  91. super.key,
  92. required this.itemBuilder,
  93. required this.itemCount,
  94. this.padding,
  95. this.isAutoScrolling = true,
  96. });
  97. @override
  98. State<StaggeredAutoScrollListView> createState() => _StaggeredAutoScrollListViewState();
  99. }
  100. class _StaggeredAutoScrollListViewState extends State<StaggeredAutoScrollListView> {
  101. final int rowCount = 3;
  102. final int loopFactor = 3;
  103. late final List<ScrollController> _controllers;
  104. late final List<Timer?> _timers;
  105. late final List<Timer?> _resumeTimers;
  106. late final List<bool> _isUserScrollingList;
  107. @override
  108. void initState() {
  109. super.initState();
  110. _controllers = List.generate(rowCount, (_) => ScrollController());
  111. _timers = List.generate(rowCount, (_) => null);
  112. _resumeTimers = List.generate(rowCount, (_) => null);
  113. _isUserScrollingList = List.generate(rowCount, (_) => false);
  114. if (widget.isAutoScrolling) {
  115. WidgetsBinding.instance.addPostFrameCallback((_) => _startAllScrolls());
  116. }
  117. }
  118. @override
  119. void dispose() {
  120. for (final controller in _controllers) {
  121. controller.dispose();
  122. }
  123. for (final timer in _timers) {
  124. timer?.cancel();
  125. }
  126. for (final timer in _resumeTimers) {
  127. timer?.cancel();
  128. }
  129. super.dispose();
  130. }
  131. void _startAllScrolls() {
  132. for (int i = 0; i < rowCount; i++) {
  133. _startScroll(i);
  134. }
  135. }
  136. void _startScroll(int rowIndex) {
  137. _timers[rowIndex]?.cancel();
  138. _timers[rowIndex] = Timer.periodic(const Duration(milliseconds: 80), (_) {
  139. if (!widget.isAutoScrolling || _isUserScrollingList[rowIndex]) return;
  140. final controller = _controllers[rowIndex];
  141. if (!controller.hasClients) return;
  142. final maxExtent = controller.position.maxScrollExtent;
  143. final current = controller.position.pixels;
  144. final next = current + 1;
  145. if (next >= maxExtent) {
  146. final viewportWidth = controller.position.viewportDimension;
  147. final contentWidth = maxExtent + viewportWidth;
  148. final originalContentWidth = contentWidth / loopFactor;
  149. final offset = next - maxExtent;
  150. controller.jumpTo(originalContentWidth + offset);
  151. } else {
  152. controller.jumpTo(next);
  153. }
  154. });
  155. }
  156. void _onUserScroll(int rowIndex) {
  157. if (_isUserScrollingList[rowIndex]) return;
  158. _isUserScrollingList[rowIndex] = true;
  159. _timers[rowIndex]?.cancel();
  160. _timers[rowIndex] = null;
  161. _resumeTimers[rowIndex]?.cancel();
  162. _resumeTimers[rowIndex] = Timer(const Duration(seconds: 1), () {
  163. _isUserScrollingList[rowIndex] = false;
  164. if (widget.isAutoScrolling) {
  165. _startScroll(rowIndex);
  166. }
  167. });
  168. }
  169. @override
  170. Widget build(BuildContext context) {
  171. final actualItemsPerRow = (widget.itemCount / rowCount).ceil();
  172. return Column(
  173. children: List.generate(rowCount, (rowIndex) {
  174. final startIndex = rowIndex * actualItemsPerRow;
  175. final endIndex = (startIndex + actualItemsPerRow).clamp(0, widget.itemCount);
  176. final rowItems = List.generate(endIndex - startIndex, (i) => startIndex + i);
  177. final extendedItems = List.generate(
  178. rowItems.length * loopFactor,
  179. (i) => rowItems[i % rowItems.length],
  180. );
  181. return SizedBox(
  182. height: 50.w, // 使用你自己的高度或比例
  183. child: NotificationListener<UserScrollNotification>(
  184. onNotification: (notification) {
  185. if (notification.direction != ScrollDirection.idle) {
  186. _onUserScroll(rowIndex);
  187. }
  188. return false;
  189. },
  190. child: ListView.builder(
  191. controller: _controllers[rowIndex],
  192. scrollDirection: Axis.horizontal,
  193. padding: widget.padding,
  194. itemCount: extendedItems.length,
  195. itemBuilder: (context, index) =>
  196. widget.itemBuilder(context, extendedItems[index]),
  197. ),
  198. ),
  199. );
  200. }),
  201. );
  202. }
  203. }