WoltModalSheet 旨在革新 Flutter 模态表单的使用。该 UI 组件采用 Wolt 级别的设计质量,并在 Wolt 产品中广泛使用,提供了一个视觉吸引力强且用户友好的模态表单,具有多页面、页面过渡的动画效果以及页面内可滚动内容。

特点

多页面布局

在一个表单内浏览多个页面。

Experience multi-page navigation in WoltModalSheet

可滚动内容

每页内容的可滚动性更强,可轻松容纳大量内容。

Scroll with ease in WoltModalSheet

响应式设计

模态表单会根据所有屏幕尺寸进行调整,在较大的屏幕上显示为对话框,在较小的屏幕上显示为底部表单,具体取决于用户指定的条件。

Adaptability to different screen sizes in WoltModalSheet

动画效果

通过动态的页面过渡和滚动动画吸引用户。

分页 滚动
Pagination Scrolling

命令式和声明式导航

该库展示了命令式和声明式导航模式的示例,用于在屏幕上显示模态表单。

Illustration of imperative and declarative navigation in WoltModalSheet

动态分页

用户输入可以动态地塑造模态表单的页面列表。

Dynamic pagination in action in WoltModalSheet

状态管理集成

Wolt Modal Sheet 中的页面提供可自定义的外观,页面组件提供 WoltModalSheetPage 类实例。API 提供了一种管理页面组件之间状态的方法,可与 Bloc 和 Provider 等流行库一起使用。

入门

要使用此插件,请在您的 pubspec.yaml 文件中将 wolt_modal_sheet 添加为依赖项。

用法

此软件包有 4 个示例项目。

示例应用程序

example 应用演示了如何显示一个双页模态表单。

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

  @override
  Widget build(BuildContext context) {
    final pageIndexNotifier = ValueNotifier(0);

    WoltModalSheetPage page1(BuildContext modalSheetContext) {
      return WoltModalSheetPage.withSingleChild(
        stickyActionBar: StickyActionBarWrapper(
          child: Column(
            children: [
              WoltElevatedButton(
                onPressed: () => Navigator.of(modalSheetContext).pop(),
                theme: WoltElevatedButtonTheme.secondary,
                child: const Text('Cancel'),
              ),
              const SizedBox(height: 8),
              WoltElevatedButton(
                onPressed: () {
                  pageIndexNotifier.value = pageIndexNotifier.value + 1;
                },
                child: const Text('Next page'),
              ),
            ],
          ),
        ),
        pageTitle: const ModalSheetTitle('Pagination'),
        topBarTitle: const ModalSheetTopBarTitle('Pagination'),
        closeButton: WoltModalSheetCloseButton(onClosed: Navigator.of(modalSheetContext).pop),
        mainContentPadding: const EdgeInsetsDirectional.all(16),
        child: const Padding(
            padding: EdgeInsets.only(bottom: 120, top: 16),
            child: Padding(
              padding: EdgeInsets.only(bottom: 16),
              child: Text(
                '''
Pagination involves a sequence of screens the user navigates sequentially. We chose a lateral motion for these transitions. When proceeding forward, the next screen emerges from the right; moving backward, the screen reverts to its original position. We felt that sliding the next screen entirely from the right could be overly distracting. As a result, we decided to move and fade in the next page using 30% of the modal side.
''',
              ),
            )),
      );
    }

    WoltModalSheetPage page2(BuildContext modalSheetContext) {
      return WoltModalSheetPage.withCustomSliverList(
        mainContentPadding: EdgeInsetsDirectional.zero,
        stickyActionBar: StickyActionBarWrapper(
          padding: EdgeInsets.zero,
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16.0),
            child: WoltElevatedButton(
              onPressed: () {
                Navigator.of(modalSheetContext).pop();
                pageIndexNotifier.value = 0;
              },
              child: const Text('Close'),
            ),
          ),
        ),
        pageTitle: const Padding(
          padding: EdgeInsets.symmetric(horizontal: 16),
          child: ModalSheetTitle('Material Colors'),
        ),
        heroImageHeight: 200,
        heroImage: const Image(
          image: AssetImage('lib/assets/images/material_colors_hero.png'),
          fit: BoxFit.cover,
        ),
        topBarTitle: const ModalSheetTopBarTitle('Material Colors'),
        backButton: WoltModalSheetBackButton(onBackPressed: () {
          pageIndexNotifier.value = pageIndexNotifier.value - 1;
        }),
        closeButton: WoltModalSheetCloseButton(onClosed: () {
          Navigator.of(modalSheetContext).pop();
          pageIndexNotifier.value = 0;
        }),
        sliverList: SliverList(
          delegate: SliverChildBuilderDelegate(
                (_, index) => ColorTile(color: allMaterialColors[index]),
            childCount: allMaterialColors.length,
          ),
        ),
      );
    }

    return MaterialApp(
      home: Scaffold(
        body: Builder(
          builder: (context) {
            return Center(
              child: SizedBox(
                width: 200,
                child: WoltElevatedButton(
                  child: const Text('Show Wolt Modal Sheet'),
                  onPressed: () {
                    WoltModalSheet.show<void>(
                      pageIndexNotifier: pageIndexNotifier,
                      context: context,
                      pageListBuilderNotifier: (modalSheetContext) {
                        return [
                          page1(modalSheetContext),
                          page2(modalSheetContext),
                        ];
                      },
                      modalTypeBuilder: (context) {
                        final size = MediaQuery.of(context).size.width;
                        if (size < 768) {
                          return WoltModalType.bottomSheet;
                        } else {
                          return WoltModalType.dialog;
                        }
                      },
                      onModalDismissedWithBarrierTap: () {
                        debugPrint('Closed modal sheet with barrier tap');
                        pageIndexNotifier.value = 0;
                      },
                      maxDialogWidth: 560,
                      minDialogWidth: 400,
                      minPageHeight: 0.4,
                      maxPageHeight: 0.9,
                    );
                  },
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

上面的代码片段生成如下内容:

Dynamic pagination in action in WoltModalSheet

具有命令式导航的 Playground 应用

playground 应用演示了如何以命令式的方式显示模态表单。此模块的目的是进行各种用例的尝试和实验。这些用例包括:它演示了

  • 一个页面强制最大高度,与其内容无关。
  • 一个带有英雄图片的页面
  • 一个列表项被惰性构建的页面。
  • 一个带有文本框的页面。

具有声明式导航的 Playground 应用

playground_navigator2 应用与 playground 应用内容相同,但模态表单是使用 Navigator 2.0(Router API)以声明式方式显示的。

咖啡机应用用于状态管理示例

最后,coffee_maker 应用演示了如何使用 Provider 状态管理库的特定用法来管理页面组件之间的状态。

代码片段演示了如何使用 Change Notifier Provider 来装饰模态表单,以便可以根据当前状态重建页面组件。

  void _onCoffeeOrderSelectedInAddWaterState(BuildContext context, String coffeeOrderId) {
  final model = context.read<StoreOnlineViewModel>();
  final pageIndexNotifier = ValueNotifier(0);

  WoltModalSheet.show(
    pageIndexNotifier: pageIndexNotifier,
    context: context,
    decorator: (child) {
      return ChangeNotifierProvider<StoreOnlineViewModel>.value(
        value: model,
        builder: (_, __) => child,
      );
    },
    pageListBuilderNotifier: AddWaterModalPageBuilder.build(
      coffeeOrderId: coffeeOrderId,
      goToPreviousPage: () => pageIndexNotifier.value = pageIndexNotifier.value - 1,
      goToNextPage: () => pageIndexNotifier.value = pageIndexNotifier.value + 1,
    ),
    modalTypeBuilder: _modalTypeBuilder,
  );
}

Dynamic pagination in action in WoltModalSheet

附加信息

这篇博文 中,我们探讨了指导 WoltModalSheet 功能的基础设计决策。

GitHub

查看 Github