|
@@ -105,11 +105,14 @@ class _AutoScrollListViewState extends State<AutoScrollListView> {
|
|
|
|
|
|
|
|
// 多行横向自动滚动列表
|
|
// 多行横向自动滚动列表
|
|
|
/// 一个支持自动滚动的多行横向列表,每一行自动向右滚动(模拟跑马灯效果)
|
|
/// 一个支持自动滚动的多行横向列表,每一行自动向右滚动(模拟跑马灯效果)
|
|
|
-class StaggeredAutoScrollListView extends StatefulWidget {
|
|
|
|
|
|
|
+class StaggeredAutoScrollListView extends StatelessWidget {
|
|
|
final NullableIndexedWidgetBuilder itemBuilder;
|
|
final NullableIndexedWidgetBuilder itemBuilder;
|
|
|
final int itemCount;
|
|
final int itemCount;
|
|
|
final EdgeInsetsGeometry? padding;
|
|
final EdgeInsetsGeometry? padding;
|
|
|
final bool isAutoScrolling;
|
|
final bool isAutoScrolling;
|
|
|
|
|
+ final int rowCount;
|
|
|
|
|
+ final int loopFactor;
|
|
|
|
|
+ final double rowHeight;
|
|
|
|
|
|
|
|
const StaggeredAutoScrollListView({
|
|
const StaggeredAutoScrollListView({
|
|
|
super.key,
|
|
super.key,
|
|
@@ -117,125 +120,34 @@ class StaggeredAutoScrollListView extends StatefulWidget {
|
|
|
required this.itemCount,
|
|
required this.itemCount,
|
|
|
this.padding,
|
|
this.padding,
|
|
|
this.isAutoScrolling = true,
|
|
this.isAutoScrolling = true,
|
|
|
|
|
+ this.rowCount = 3,
|
|
|
|
|
+ this.loopFactor = 3,
|
|
|
|
|
+ this.rowHeight = 50.0,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
@override
|
|
@override
|
|
|
- State<StaggeredAutoScrollListView> createState() => _StaggeredAutoScrollListViewState();
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-class _StaggeredAutoScrollListViewState extends State<StaggeredAutoScrollListView> {
|
|
|
|
|
- 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();
|
|
|
|
|
- _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());
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- @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++) {
|
|
|
|
|
- _startScroll(i);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- 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) {
|
|
Widget build(BuildContext context) {
|
|
|
- final actualItemsPerRow = (widget.itemCount / rowCount).ceil();
|
|
|
|
|
|
|
+ final itemsPerRow = (itemCount / rowCount).ceil();
|
|
|
|
|
|
|
|
return Column(
|
|
return Column(
|
|
|
children: List.generate(rowCount, (rowIndex) {
|
|
children: List.generate(rowCount, (rowIndex) {
|
|
|
- final startIndex = rowIndex * actualItemsPerRow;
|
|
|
|
|
- final endIndex = (startIndex + actualItemsPerRow).clamp(0, widget.itemCount);
|
|
|
|
|
|
|
+ final startIndex = rowIndex * itemsPerRow;
|
|
|
|
|
+ final endIndex = (startIndex + itemsPerRow).clamp(0, itemCount);
|
|
|
final rowItems = List.generate(endIndex - startIndex, (i) => startIndex + i);
|
|
final rowItems = List.generate(endIndex - startIndex, (i) => startIndex + i);
|
|
|
|
|
+
|
|
|
final extendedItems = List.generate(
|
|
final extendedItems = List.generate(
|
|
|
rowItems.length * loopFactor,
|
|
rowItems.length * loopFactor,
|
|
|
(i) => rowItems[i % rowItems.length],
|
|
(i) => rowItems[i % rowItems.length],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
return SizedBox(
|
|
return SizedBox(
|
|
|
- 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]),
|
|
|
|
|
- ),
|
|
|
|
|
|
|
+ height: rowHeight.w,
|
|
|
|
|
+ child: AutoScrollListView(
|
|
|
|
|
+ itemBuilder: (context, index) => itemBuilder(context, extendedItems[index]),
|
|
|
|
|
+ itemCount: extendedItems.length,
|
|
|
|
|
+ scrollDirection: Axis.horizontal,
|
|
|
|
|
+ isAutoScrolling: isAutoScrolling,
|
|
|
|
|
+ padding: padding,
|
|
|
),
|
|
),
|
|
|
);
|
|
);
|
|
|
}),
|
|
}),
|