|
|
@@ -106,16 +106,9 @@ class _AutoScrollListViewState extends State<AutoScrollListView> {
|
|
|
// 多行横向自动滚动列表
|
|
|
/// 一个支持自动滚动的多行横向列表,每一行自动向右滚动(模拟跑马灯效果)
|
|
|
class StaggeredAutoScrollListView extends StatefulWidget {
|
|
|
- /// 子项构建器(需根据 itemIndex 构建对应组件)
|
|
|
final NullableIndexedWidgetBuilder itemBuilder;
|
|
|
-
|
|
|
- /// 实际数据项数量(不是扩展后的数量)
|
|
|
final int itemCount;
|
|
|
-
|
|
|
- /// 每行的内边距
|
|
|
final EdgeInsetsGeometry? padding;
|
|
|
-
|
|
|
- /// 是否启用自动滚动
|
|
|
final bool isAutoScrolling;
|
|
|
|
|
|
const StaggeredAutoScrollListView({
|
|
|
@@ -131,17 +124,22 @@ class StaggeredAutoScrollListView extends StatefulWidget {
|
|
|
}
|
|
|
|
|
|
class _StaggeredAutoScrollListViewState extends State<StaggeredAutoScrollListView> {
|
|
|
- final int rowCount = 3; // 列表的总行数
|
|
|
- final List<ScrollController> _controllers = []; // 每一行的滚动控制器
|
|
|
- final List<Timer?> _timers = []; // 每一行对应的自动滚动定时器
|
|
|
- final int loopFactor = 3; // 每行内容扩展的倍数,防止滚动过短
|
|
|
+ final int rowCount = 3;
|
|
|
+ final int loopFactor = 3;
|
|
|
+
|
|
|
+ late final List<ScrollController> _controllers;
|
|
|
+ late final List<Timer?> _timers;
|
|
|
+ late final List<Timer?> _resumeTimers;
|
|
|
+ late final List<bool> _isUserScrollingList;
|
|
|
|
|
|
@override
|
|
|
void initState() {
|
|
|
super.initState();
|
|
|
- for (int i = 0; i < rowCount; i++) {
|
|
|
- _controllers.add(ScrollController());
|
|
|
- }
|
|
|
+ _controllers = List.generate(rowCount, (_) => ScrollController());
|
|
|
+ _timers = List.generate(rowCount, (_) => null);
|
|
|
+ _resumeTimers = List.generate(rowCount, (_) => null);
|
|
|
+ _isUserScrollingList = List.generate(rowCount, (_) => false);
|
|
|
+
|
|
|
if (widget.isAutoScrolling) {
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) => _startAllScrolls());
|
|
|
}
|
|
|
@@ -149,47 +147,68 @@ class _StaggeredAutoScrollListViewState extends State<StaggeredAutoScrollListVie
|
|
|
|
|
|
@override
|
|
|
void dispose() {
|
|
|
- // 销毁滚动控制器
|
|
|
for (final controller in _controllers) {
|
|
|
controller.dispose();
|
|
|
}
|
|
|
-
|
|
|
- // 取消所有定时器
|
|
|
for (final timer in _timers) {
|
|
|
timer?.cancel();
|
|
|
}
|
|
|
-
|
|
|
+ for (final timer in _resumeTimers) {
|
|
|
+ timer?.cancel();
|
|
|
+ }
|
|
|
super.dispose();
|
|
|
}
|
|
|
|
|
|
- /// 启动所有行的自动滚动
|
|
|
void _startAllScrolls() {
|
|
|
for (int i = 0; i < rowCount; i++) {
|
|
|
- final controller = _controllers[i];
|
|
|
- _timers.add(Timer.periodic(const Duration(milliseconds: 50), (_) {
|
|
|
- if (!controller.hasClients) return;
|
|
|
-
|
|
|
- final maxExtent = controller.position.maxScrollExtent;
|
|
|
- final current = controller.position.pixels;
|
|
|
- final next = current + 1;
|
|
|
-
|
|
|
- if (next >= maxExtent) {
|
|
|
- final viewportWidth = controller.position.viewportDimension;
|
|
|
- final contentWidth = maxExtent + viewportWidth;
|
|
|
- final originalContentWidth = contentWidth / loopFactor;
|
|
|
- final offset = next - maxExtent;
|
|
|
- controller.jumpTo(originalContentWidth + offset);
|
|
|
- } else {
|
|
|
- controller.jumpTo(next);
|
|
|
- }
|
|
|
- }));
|
|
|
+ _startScroll(i);
|
|
|
}
|
|
|
}
|
|
|
- @override
|
|
|
+
|
|
|
+ void _startScroll(int rowIndex) {
|
|
|
+ _timers[rowIndex]?.cancel();
|
|
|
+ _timers[rowIndex] = Timer.periodic(const Duration(milliseconds: 80), (_) {
|
|
|
+ if (!widget.isAutoScrolling || _isUserScrollingList[rowIndex]) return;
|
|
|
+
|
|
|
+ final controller = _controllers[rowIndex];
|
|
|
+ if (!controller.hasClients) return;
|
|
|
+
|
|
|
+ final maxExtent = controller.position.maxScrollExtent;
|
|
|
+ final current = controller.position.pixels;
|
|
|
+ final next = current + 1;
|
|
|
+
|
|
|
+ if (next >= maxExtent) {
|
|
|
+ final viewportWidth = controller.position.viewportDimension;
|
|
|
+ final contentWidth = maxExtent + viewportWidth;
|
|
|
+ final originalContentWidth = contentWidth / loopFactor;
|
|
|
+ final offset = next - maxExtent;
|
|
|
+ controller.jumpTo(originalContentWidth + offset);
|
|
|
+ } else {
|
|
|
+ controller.jumpTo(next);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ void _onUserScroll(int rowIndex) {
|
|
|
+ if (_isUserScrollingList[rowIndex]) return;
|
|
|
+
|
|
|
+ _isUserScrollingList[rowIndex] = true;
|
|
|
+ _timers[rowIndex]?.cancel();
|
|
|
+ _timers[rowIndex] = null;
|
|
|
+
|
|
|
+ _resumeTimers[rowIndex]?.cancel();
|
|
|
+ _resumeTimers[rowIndex] = Timer(const Duration(seconds: 1), () {
|
|
|
+ _isUserScrollingList[rowIndex] = false;
|
|
|
+ if (widget.isAutoScrolling) {
|
|
|
+ _startScroll(rowIndex);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
@override
|
|
|
Widget build(BuildContext context) {
|
|
|
final actualItemsPerRow = (widget.itemCount / rowCount).ceil();
|
|
|
+
|
|
|
return Column(
|
|
|
children: List.generate(rowCount, (rowIndex) {
|
|
|
final startIndex = rowIndex * actualItemsPerRow;
|
|
|
@@ -199,14 +218,24 @@ class _StaggeredAutoScrollListViewState extends State<StaggeredAutoScrollListVie
|
|
|
rowItems.length * loopFactor,
|
|
|
(i) => rowItems[i % rowItems.length],
|
|
|
);
|
|
|
+
|
|
|
return SizedBox(
|
|
|
- height: 50.w,
|
|
|
- child: ListView.builder(
|
|
|
- controller: _controllers[rowIndex],
|
|
|
- scrollDirection: Axis.horizontal,
|
|
|
- padding: widget.padding,
|
|
|
- itemCount: extendedItems.length,
|
|
|
- itemBuilder: (context, index) => widget.itemBuilder(context, extendedItems[index]),
|
|
|
+ height: 50.w, // 使用你自己的高度或比例
|
|
|
+ child: NotificationListener<UserScrollNotification>(
|
|
|
+ onNotification: (notification) {
|
|
|
+ if (notification.direction != ScrollDirection.idle) {
|
|
|
+ _onUserScroll(rowIndex);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ },
|
|
|
+ child: ListView.builder(
|
|
|
+ controller: _controllers[rowIndex],
|
|
|
+ scrollDirection: Axis.horizontal,
|
|
|
+ padding: widget.padding,
|
|
|
+ itemCount: extendedItems.length,
|
|
|
+ itemBuilder: (context, index) =>
|
|
|
+ widget.itemBuilder(context, extendedItems[index]),
|
|
|
+ ),
|
|
|
),
|
|
|
);
|
|
|
}),
|