Flutter多方向水平滚动列表插件multi_directional_horizontal_list的使用

Flutter多方向水平滚动列表插件multi_directional_horizontal_list的使用

Pub Version

一个多方向水平滚动的ListView

特性

  • 简单创建类似时间轴的界面。
  • 轻松实现前后通信。
  • 可以通过编程方式跳转或动画到特定索引。

开始使用

在你的pubspec.yaml文件中添加插件:

dependencies:
  multi_directional_horizontal_list: ^0.0.3

在你的Dart文件中导入库:

import 'package:multi_directional_horizontal_list/multi_directional_horizontal_list.dart';

创建一个MultiDirectionalHorizontalList小部件:

final MultiDirectionalHorizontalListController controller = MultiDirectionalHorizontalListController();

MultiDirectionalHorizontalList(
  controller: controller,
  itemCount: 20,
  onLeftLoaded: () {
    print("Reached left end");
  },
  onRightLoaded: () {
    print("Reached right end");
  },
  itemBuilder: (context, index) {
    return SizedBox(); // 这里可以替换为你的自定义组件
  }
);

如果你使用了MultiDirectionalHorizontalListController,你可以通过编程方式滚动到特定位置:

通过索引滚动到元素的位置:

controller.animateTo(4); // 平滑滚动到索引4
controller.jumpTo(4);   // 立即跳转到索引4

你也可以附加监听器来监听滚动事件,例如方向或位置:

controller.addListener((event) {
  print('Current Position: ${event.position}');
});

完整示例

以下是一个完整的示例,展示了如何使用multi_directional_horizontal_list插件创建一个日期选择器。

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:multi_directional_horizontal_list/multi_directional_horizontal_list.dart';
import 'package:intl/intl.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '多方向水平滚动列表演示',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.pinkAccent),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: ''),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final MultiDirectionalHorizontalListController? testingController;

  const MyHomePage({
    super.key,
    required this.title,
    this.testingController,
  });

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late MultiDirectionalHorizontalListController controller;
  DateTime _selectedDateStart = DateTime.now();
  int _activeIndex = 0;

  final double itemWidth = 85;

  late double middle = -(MediaQuery.sizeOf(context).width) / 2 + itemWidth / 2;

  [@override](/user/override)
  initState() {
    controller =
        widget.testingController ?? MultiDirectionalHorizontalListController()
          ..addListener((event) {
            _handleCallbackEvent(event);
          });
    super.initState();
  }

  void _handleCallbackEvent(ScrollEvent? event) {
    event?.randomCallback?.call();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          // 月和年
          Text(
            _selectedDateStart.monthAndDay,
            key: const ValueKey(0),
            maxLines: 1,
            textAlign: TextAlign.center,
            style: GoogleFonts.lato(
              textStyle: Theme.of(context).textTheme.headlineMedium,
            ),
          ),

          MultiDirectionalHorizontalList.builder(
            controller: controller,
            initialScrollOffset: middle,
            itemCount: 100,
            height: 60,
            onLeftLoaded: () {},
            onRightLoaded: () {},
            itemBuilder: (context, index) {
              // 当前日期时间
              DateTime currentDateTime = DateTime(
                DateTime.now().year,
                DateTime.now().month,
                DateTime.now().day + index,
              );

              bool isSelected = currentDateTime.isSelected(_selectedDateStart);

              bool isToday = currentDateTime.isToday;

              return InkWell(
                splashFactory: NoSplash.splashFactory,
                highlightColor: Colors.transparent,
                onTap: () {
                  setState(() {
                    _selectedDateStart = currentDateTime;
                    _activeIndex = index;
                  });
                },
                child: SizedBox(
                  width: itemWidth,
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      NotificationListener(
                        onNotification:
                            (SizeChangedLayoutNotification notification) {
                          controller.animateTo(
                            middle + (_activeIndex - 1) * itemWidth + itemWidth,
                          );
                          return true;
                        },
                        child: SizeChangedLayoutNotifier(
                          child: AnimatedSwitcher(
                            duration: const Duration(milliseconds: 300),
                            child: isSelected
                                ? Stack(
                                    children: [
                                      Positioned(
                                        top: 0,
                                        right: 0,
                                        child: isToday
                                            ? const RedDot()
                                            : const SizedBox.shrink(),
                                      ),
                                      Text(
                                        currentDateTime.getDay(
                                            abbreviate: false),
                                        key: const ValueKey(0),
                                        maxLines: 1,
                                        textAlign: TextAlign.center,
                                        style: GoogleFonts.lato(
                                          textStyle: Theme.of(context)
                                              .textTheme
                                              .titleMedium,
                                        ),
                                      ),
                                    ],
                                  )
                                : Stack(
                                    children: [
                                      Positioned(
                                        top: 0,
                                        right: 0,
                                        child: isToday
                                            ? const RedDot()
                                            : const SizedBox.shrink(),
                                      ),
                                      Text(
                                        currentDateTime.getDay(
                                            abbreviate: true),
                                        key: const ValueKey(1),
                                        maxLines: 1,
                                        textAlign: TextAlign.center,
                                        style: GoogleFonts.lato(
                                          textStyle: Theme.of(context)
                                              .textTheme
                                              .titleSmall
                                              ?.copyWith(
                                                color: Colors.black54,
                                              ),
                                        ),
                                      ),
                                    ],
                                  ),
                          ),
                        ),
                      ),
                      AnimatedSwitcher(
                        duration: const Duration(milliseconds: 300),
                        child: isSelected
                            ? Text(
                                currentDateTime.dayInNo,
                                key: const ValueKey(0),
                                style: GoogleFonts.lato(
                                  textStyle: Theme.of(context)
                                      .textTheme
                                      .labelMedium
                                      ?.copyWith(color: Colors.black),
                                ),
                              )
                            : Text(
                                currentDateTime.dayInNo,
                                key: const ValueKey(1),
                                style: GoogleFonts.lato(
                                  textStyle: Theme.of(context)
                                      .textTheme
                                      .labelSmall
                                      ?.copyWith(color: Colors.black54),
                                ),
                              ),
                      ),
                      const SizedBox(
                        height: 8,
                      ),
                      AnimatedScale(
                        duration: const Duration(milliseconds: 600),
                        scale: isSelected ? 1 : 0,
                        curve: isSelected
                            ? Curves.decelerate
                            : Curves.easeOutQuart,
                        child: Container(
                          decoration: const BoxDecoration(
                            borderRadius: BorderRadius.only(
                              topRight: Radius.circular(40),
                              topLeft: Radius.circular(40),
                            ),
                            color: Colors.black54,
                          ),
                          width: itemWidth,
                          height: 4,
                        ),
                      )
                    ],
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

extension DateUtils on DateTime {
  bool get isToday {
    final now = DateTime.now();
    return now.day == day && now.month == month && now.year == year;
  }

  bool get isTomorrow {
    final tomorrow = DateTime.now().add(const Duration(days: 1));
    return tomorrow.day == day &&
        tomorrow.month == month &&
        tomorrow.year == year;
  }

  bool get isYesterday {
    final yesterday = DateTime.now().subtract(const Duration(days: 1));
    return yesterday.day == day &&
        yesterday.month == month &&
        yesterday.year == year;
  }

  bool isSelected(DateTime selected) {
    return selected.day == day &&
        selected.month == month &&
        selected.year == year;
  }

  String get monthAndDay {
    return DateFormat.yMMMM().format(this);
  }

  String get dayInNo {
    return DateFormat.d().format(this);
  }

  String getMonth({bool includeYear = false}) {
    if (includeYear) {
      return DateFormat.yMMMM().format(this);
    }
    return DateFormat.MMMM().format(this);
  }

  String getDay({bool abbreviate = false, bool alphabet = false}) {
    if (alphabet) {
      return DateFormat.EEEEE().format(this);
    }
    if (abbreviate) {
      return DateFormat.E().format(this);
    }
    return DateFormat.EEEE().format(this);
  }
}

class RedDot extends StatelessWidget {
  const RedDot({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return FractionalTranslation(
      translation: const Offset(1.0, -1.0),
      child: Container(
        height: 5,
        width: 5,
        decoration: const BoxDecoration(
          shape: BoxShape.circle,
          color: Colors.redAccent,
        ),
      ),
    );
  }
}

更多关于Flutter多方向水平滚动列表插件multi_directional_horizontal_list的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter多方向水平滚动列表插件multi_directional_horizontal_list的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter中使用multi_directional_horizontal_list插件的一个示例代码案例。这个插件允许你创建一个多方向水平滚动的列表。首先,你需要确保已经在pubspec.yaml文件中添加了该插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  multi_directional_horizontal_list: ^最新版本号  # 请替换为最新版本号

然后,运行flutter pub get来安装依赖。

接下来,下面是一个使用multi_directional_horizontal_list的示例代码:

import 'package:flutter/material.dart';
import 'package:multi_directional_horizontal_list/multi_directional_horizontal_list.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Multi-Directional Horizontal List Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MultiDirectionalHorizontalListDemo(),
    );
  }
}

class MultiDirectionalHorizontalListDemo extends StatefulWidget {
  @override
  _MultiDirectionalHorizontalListDemoState createState() => _MultiDirectionalHorizontalListDemoState();
}

class _MultiDirectionalHorizontalListDemoState extends State<MultiDirectionalHorizontalListDemo> {
  final List<String> items = List.generate(50, (index) => "Item $index");

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Multi-Directional Horizontal List Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: MultiDirectionalHorizontalList(
          // 初始化水平方向滚动列表
          itemCount: items.length,
          itemBuilder: (context, index) {
            return Container(
              margin: EdgeInsets.symmetric(horizontal: 8.0),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.grey.withOpacity(0.5)),
                borderRadius: BorderRadius.circular(8),
              ),
              child: Center(
                child: Text(
                  items[index],
                  style: TextStyle(fontSize: 18),
                ),
              ),
            );
          },
          // 配置滚动方向
          scrollDirections: [
            Axis.horizontal, // 水平方向
            // 你可以添加更多方向,例如 Axis.vertical,但这通常不是此插件的主要用途
          ],
          // 其他可选配置
          scrollPhysics: BouncingScrollPhysics(),
          controller: ScrollController(),
        ),
      ),
    );
  }
}

在这个示例中,我们做了以下几件事:

  1. 添加依赖:在pubspec.yaml文件中添加了multi_directional_horizontal_list依赖。
  2. 创建主应用:使用MaterialApp创建了一个简单的Flutter应用。
  3. 创建演示页面:定义了一个MultiDirectionalHorizontalListDemo页面,该页面包含一个MultiDirectionalHorizontalList组件。
  4. 配置列表:在MultiDirectionalHorizontalList中,我们配置了itemCountitemBuilder来生成列表项,并指定了滚动方向为Axis.horizontal

请注意,multi_directional_horizontal_list插件的具体API和使用方式可能会随着版本更新而变化,因此请参考最新的官方文档和示例代码。如果插件支持更多复杂的滚动方向和配置,你可以进一步探索其API文档。

回到顶部