Flutter辅助开发插件flutter_wizard的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter辅助开发插件flutter_wizard的使用

Flutter Wizard 是由 Jop Middelkamp 开发的一个库,它使创建自定义向导变得简单。你可以完全控制向导的外观。

使用方法

包裹你的小部件与向导控制器

要开始使用 flutter_wizard,你需要将所有向导组件包裹在 DefaultWizardController 中,它可以接收几个参数:

  • stepControllers (必需):一个 WizardStepController 列表,更多细节将在后面的章节中描述。
  • initialIndex:初始显示的步骤索引。
  • onStepChanged:当步骤更改时触发的回调。
  • onControllerCreated:当 WizardController 创建时触发的回调。
  • child (必需)DefaultWizardController 的子小部件。这个小部件(或它的子级之一)应该包含所有需要与 Wizard 小部件交互的小部件。
DefaultWizardController(
  stepControllers: [
    WizardStepController(
      step: provider.stepOneProvider,
    ),
    WizardStepController(
      step: provider.stepTwoProvider,
      isNextEnabled: false,
    ),
    WizardStepController(
      step: provider.stepThreeProvider,
    ),
  ],
  child: Column(
    children: [
      ProgressIndicator(),
      Expanded(
        child: Wizard(
          ...
        ),
      ),
      ActionBar(),
    ],
  ),
);

步骤状态管理

你可以为 DefaultWizardController 提供 stepControllers。这些步骤控制器需要提供一个步骤状态。这个步骤状态可以是 bloc、cubit、provider、notifier 等等。唯一的要求是在你的状态管理对象中混入 WizardStep

当你混入 WizardStep 后,你的状态管理对象将被扩展以下属性和方法:

  • wizardController:包含 WizardController 的属性。
  • onControllerReceived:当 wizardController 属性被设置时触发的回调。
  • onShowing:当步骤开始显示时触发的回调。
  • onShowingCompleted:当步骤完成显示时触发的回调。
  • onHiding:当步骤开始隐藏时触发的回调。
  • onHidingCompleted:当步骤完成隐藏时触发的回调。

示例:

class StepOneProvider with WizardStep {
  StepOneProvider();

  final _description = BehaviorSubject<String>.seeded('');

  final descriptionFocusNode = FocusNode();

  final descriptionTextController = TextEditingController();

  String get description {
    return _description.value;
  }

  @override
  Future<void> onShowing() async {
    if (_description.value.isEmpty) {
      descriptionFocusNode.requestFocus();
    }
  }

  @override
  Future<void> onHiding() async {
    if (descriptionFocusNode.hasFocus) {
      descriptionFocusNode.unfocus();
    }
  }

  void updateDescription(String description) {
    _description.add(description);
  }

  void dispose() {
    descriptionTextController.dispose();
  }
}

确定每个步骤显示的小部件

现在我们来设置每个步骤显示的小部件。我们需要使用 Wizard 小部件。这个小部件只接受一个参数:

  • stepBuilder:用于构建步骤对应小部件的构建方法。构建方法提供了 BuildContext 和向导步骤状态。根据状态的类类型确定要显示的小部件,并将状态传递给该小部件以显示给用户。

示例:

Wizard(
  stepBuilder: (context, state) {
    if (state is StepOneProvider) {
      return StepOne(
        provider: state,
      );
    }
    if (state is StepTwoProvider) {
      return StepTwo(
        provider: state,
      );
    }
    if (state is StepThreeProvider) {
      return StepThree(
        provider: state,
      );
    }
    return Container();
  },
);

自定义小部件与向导的交互

你可以通过调用 WizardController.of(context) 或提供的扩展 context.wizardController 来获取 WizardController,从而与 Wizard 小部件进行交互。

示例:移动到下一步按钮

return StreamBuilder<bool>(
  stream: context.wizardController.getIsGoNextEnabledStream(),
  initialData: context.wizardController.getIsGoNextEnabled(),
  builder: (context, snapshot) {
    if (!snapshot.hasData || snapshot.hasError) {
      return const SizedBox.shrink();
    }
    final enabled = snapshot.data!;
    return ElevatedButton(
      child: const Text("Next"),
      onPressed: enabled ? context.wizardController.next : null,
    );
  },
);

对向导事件做出反应

为了了解向导上触发的事件,你可以在小部件树中添加 WizardEventListener

示例:

WizardEventListener(
  listener: (context, event) {
    if (event is WizardForcedGoBackToEvent) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: Text(
          'Step ${event.toIndex + 2} got disabled so the wizard is moving back to step ${event.toIndex + 1}.',
        ),
        dismissDirection: DismissDirection.horizontal,
      ));
    }
  },
),

示例代码

完整的示例代码如下所示:

import 'package:flutter/material.dart';
import 'package:flutter_wizard/flutter_wizard.dart';
import 'package:provider/provider.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Wizard Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        colorScheme: ColorScheme(
          brightness: Brightness.light,
          primary: Colors.blue,
          onPrimary: Colors.white,
          secondary: Colors.orange,
          onSecondary: Colors.white,
          surface: Colors.grey.shade100,
          onSurface: Colors.grey.shade700,
          background: Colors.white,
          onBackground: Colors.grey.shade700,
          error: Colors.redAccent,
          onError: Colors.white,
        ),
        progressIndicatorTheme: ProgressIndicatorThemeData(
          linearTrackColor: Colors.orange.shade100,
          color: Colors.orange,
        ),
      ),
      home: ProviderExamplePage.provider(),
    );
  }
}

class ProviderExamplePage extends StatelessWidget {
  const ProviderExamplePage._({Key? key}) : super(key: key);

  static Provider provider({Key? key}) {
    return Provider<ProviderExamplePageProvider>(
      create: (_) => ProviderExamplePageProvider(),
      dispose: (_, provider) => provider.dispose(),
      child: ProviderExamplePage._(
        key: key,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final provider = Provider.of<ProviderExamplePageProvider>(
      context,
    );
    return DefaultWizardController(
      stepControllers: [
        WizardStepController(
          step: provider.stepOneProvider,
        ),
        WizardStepController(
          step: provider.stepTwoProvider,
          isNextEnabled: false,
        ),
        WizardStepController(
          step: provider.stepThreeProvider,
        ),
      ],
      child: Builder(
        builder: (context) {
          return Scaffold(
            appBar: AppBar(
              title: StreamBuilder<int>(
                stream: context.wizardController.indexStream,
                initialData: context.wizardController.index,
                builder: (context, snapshot) {
                  return Text("Wizard Example - Step ${snapshot.data! + 1}");
                },
              ),
            ),
            body: WizardEventListener(
              listener: (context, event) {
                debugPrint('### ${event.runtimeType} received');
                if (event is WizardForcedGoBackToEvent) {
                  ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                    content: Text(
                      'Step ${event.toIndex + 2} got disabled so the wizard is moving back to step ${event.toIndex + 1}.',
                    ),
                    dismissDirection: DismissDirection.horizontal,
                  ));
                }
              },
              child: LayoutBuilder(
                builder: (context, constraints) {
                  return Column(
                    children: [
                      _buildProgressIndicator(
                        context,
                      ),
                      Expanded(
                        child: _buildWizard(
                          context,
                          provider: provider,
                          constraints: constraints,
                        ),
                      ),
                      const ActionBar(),
                    ],
                  );
                },
              ),
            ),
          );
        },
      ),
    );
  }

  Widget _buildWizard(
    BuildContext context, {
    required ProviderExamplePageProvider provider,
    required BoxConstraints constraints,
  }) {
    final wizard = Wizard(
      stepBuilder: (context, state) {
        if (state is StepOneProvider) {
          return StepOne(
            provider: state,
          );
        }
        if (state is StepTwoProvider) {
          return StepTwo(
            provider: state,
          );
        }
        if (state is StepThreeProvider) {
          return StepThree(
            provider: state,
          );
        }
        return Container();
      },
    );
    final narrow = constraints.maxWidth <= 500;
    if (narrow) {
      return wizard;
    }
    return Row(
      children: [
        const SizedBox(
          width: 200,
          child: StepsOverview(),
        ),
        Expanded(
          child: wizard,
        ),
      ],
    );
  }

  Widget _buildProgressIndicator(
    BuildContext context,
  ) {
    return StreamBuilder<int>(
      stream: context.wizardController.indexStream,
      initialData: context.wizardController.index,
      builder: (context, snapshot) {
        if (!snapshot.hasData || snapshot.hasError) {
          return const SizedBox.shrink();
        }
        final index = snapshot.data!;
        return StepsProgressIndicator(
          count: context.wizardController.stepCount,
          index: index,
        );
      },
    );
  }
}

class ProviderExamplePageProvider {
  ProviderExamplePageProvider()
      : stepOneProvider = StepOneProvider(),
        stepTwoProvider = StepTwoProvider(),
        stepThreeProvider = StepThreeProvider();

  final StepOneProvider stepOneProvider;
  final StepTwoProvider stepTwoProvider;
  final StepThreeProvider stepThreeProvider;

  Future<void> reportIssue() async {
    debugPrint('Finished!');
  }

  Future<void> dispose() async {
    stepOneProvider.dispose();
    stepTwoProvider.dispose();
  }
}

以上就是 flutter_wizard 插件的详细使用方法及示例代码。希望对你有所帮助!


更多关于Flutter辅助开发插件flutter_wizard的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter辅助开发插件flutter_wizard的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,flutter_wizard 是一个用于简化 Flutter 开发流程的插件,通常用于创建引导页(Wizard Pages)或分步表单。虽然具体的实现可能依赖于插件的版本和你的具体需求,但我可以提供一个基本的代码示例来展示如何使用 flutter_wizard 插件来创建一个简单的分步表单。

首先,确保你已经在 pubspec.yaml 文件中添加了 flutter_wizard 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_wizard: ^最新版本号  # 替换为实际的最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来,是一个使用 flutter_wizard 创建分步表单的示例代码:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Wizard Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: WizardDemo(),
    );
  }
}

class WizardDemo extends StatefulWidget {
  @override
  _WizardDemoState createState() => _WizardDemoState();
}

class _WizardDemoState extends State<WizardDemo> {
  final GlobalKey<WizardControllerState> _wizardController = GlobalKey<WizardControllerState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Wizard Demo'),
      ),
      body: Wizard(
        key: _wizardController,
        steps: [
          WizardStep(
            title: 'Step 1',
            content: TextFormField(
              decoration: InputDecoration(labelText: 'Enter your name'),
            ),
          ),
          WizardStep(
            title: 'Step 2',
            content: TextFormField(
              decoration: InputDecoration(labelText: 'Enter your email'),
              keyboardType: TextInputType.emailAddress,
            ),
          ),
          WizardStep(
            title: 'Step 3',
            content: TextFormField(
              decoration: InputDecoration(labelText: 'Enter your password'),
              obscureText: true,
            ),
          ),
        ],
        doneButtonTitle: 'Submit',
        onDone: () async {
          // 获取用户输入的数据
          final WizardController controller = _wizardController.currentState!;
          final Map<String, dynamic> values = await controller.collectAll();
          print('Collected values: $values');

          // 这里可以添加提交数据的逻辑,比如发送到服务器
          ScaffoldMessenger.of(context).showSnackBar(
            SnackBar(
              content: Text('Form submitted successfully!'),
            ),
          );
        },
      ),
    );
  }
}

在这个示例中,我们创建了一个包含三个步骤的向导:

  1. 第一个步骤要求用户输入名字。
  2. 第二个步骤要求用户输入电子邮件。
  3. 第三个步骤要求用户输入密码。

每个步骤都使用 TextFormField 来获取用户输入。当用户完成所有步骤并点击“Submit”按钮时,onDone 回调函数将被调用,并且可以通过 controller.collectAll() 方法收集所有步骤的数据。

请注意,这个示例是基于假设的 flutter_wizard 插件的用法,实际使用时,你可能需要参考插件的官方文档来调整代码以适应最新的 API 和功能。

回到顶部