在Flutter中使用Stepper部件创建一个多步骤的表单

187 阅读7分钟

大多数时候,填写有很多字段的表格会很麻烦,这可能会使用户不愿意完成这个过程。这就是多步骤表格发挥作用的地方。

多步骤表单正如其名称所暗示的那样:多步骤表单是将一个长的表单分解成短的部分。它们为您的应用程序的用户提供了一个不太令人生畏的体验。

Flutter预装了一个Stepper部件,使我们有能力将我们的表单分解成一个个步骤。在这篇文章中,我们将探讨什么是Stepper部件,以及如何在Flutter中应用它来构建多步骤表单以提高用户体验。您还将学习如何定制Stepper小组件以满足您的移动应用程序的规格。

步进器小部件的属性

这些是Stepper小组件的一些基本属性。我们也会通过演示来展示它们,但在开始之前,你可以在这里回顾一下它们:

  • 类型(StepperType.horizontalStepperType.vertical )--这决定了方向和每个步骤将如何放置,相对于彼此而言
  • 步骤 - 步进器的步骤,其标题、字幕和图标总是可见的。下面是一个我们将用于演示的步骤的例子。
    Account Info Address
  • currentStep - 步骤的索引值(0、1、2等)。定义了表格中的活动步骤
  • onStepContinue() - 一个回调--当--继续的按钮,进入下一个步骤
  • onStepCancel() - 一个回调-取消按钮,移动到上一个步骤。
  • onStepTapped (int index) - 当用户点击步骤时的回调,以移动到正在进行的选定的步骤。该回调也为我们提供了用户点击的步骤的索引。
  • List<Step> - 一个步长的步骤,其标题和内容会在各自的步骤中显示出来。Active

为了进一步深入了解,一个步骤本身的属性是:

  • title - 使用此属性来命名该步骤。这个属性是一个必需的属性,接受一个小部件作为值,通常是一个文本小部件
  • subtitle - 使用这个属性为步骤添加一个副标题。它接受一个小部件作为值,通常是一个文本小部件。
  • content - 我们将使用此属性来为该步骤提供内容。它是一个必需的属性,接受任何小组件作为一个值
  • state - 使用这个属性来设置步骤的状态,如 , , , , 或 。根据这个状态,步骤的图标会发生变化。completed disabled editing indexed error
  • isActive - 使用此属性来指示步骤是活动还是不活动。它接受一个布尔值作为值

在Flutter中创建一个多步骤表单

现在,让我们创建一个新的Flutter项目,展示如何应用Stepper widget。

我们的表单的初始状态看起来就像我们下面的图片。然而,我们将使用步进器部件将其分成多个步骤,这样用户就不会被他们必须填写的字段数量所淹没。

Stepper Widget

我们有可重复使用的自定义部件,我们已经创建了这些部件。

我们有我们的main.dart file ,它有以下内容:

import 'package:flutter/material.dart';
import 'package:stepper_widget/form_page.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 Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const FormPage(),
    );
  }
}

我们的FormPage widget容纳了这些内容:

import 'package:flutter/material.dart';
import 'package:stepper_widget/widgets/custom_button.dart';
import 'package:stepper_widget/widgets/custom_input.dart';

class FormPage extends StatefulWidget {
  const FormPage({Key? key}) : super(key: key);

  @override
  _FormPageState createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            "Stepper Widget ",
          ),
          centerTitle: true,
        ),
        body: Container(
          padding: const EdgeInsets.all(20),
          child: ListView(
            children: [
              const CustomInput(
                hint: "First Name",
                inputBorder: OutlineInputBorder(),
              ),
              const CustomInput(
                hint: "Last Name",
                inputBorder: OutlineInputBorder(),
              ),
              const CustomInput(
                hint: "Address",
                inputBorder: OutlineInputBorder(),
              ),
              const CustomInput(
                hint: "City and State",
                inputBorder: OutlineInputBorder(),
              ),
              const CustomInput(
                hint: "Bio",
                inputBorder: OutlineInputBorder(),
              ),
              const CustomInput(
                hint: "Bio",
                inputBorder: OutlineInputBorder(),
              ),
              CustomBtn(
                title: const Text(
                  "Save",
                  style: TextStyle(color: Colors.white),
                ),
                callback: () {},
              )
            ],
          ),
        ),
      ),
    );
  }
}

我们的CustomInput

import 'package:flutter/material.dart';

class CustomInput extends StatelessWidget {
  final ValueChanged<String>? onChanged;
  final String? hint;
  final InputBorder? inputBorder;
  const CustomInput({Key? key, this.onChanged, this.hint, this.inputBorder})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(bottom: 10),
      child: TextField(
        onChanged: (v) => onChanged!(v),
        decoration: InputDecoration(hintText: hint!, border: inputBorder),
      ),
    );
  }
}

最后是我们的自定义按钮,CustomBtn

import 'package:flutter/material.dart';

class CustomBtn extends StatelessWidget {
  final Function? callback;
  final Widget? title;
  CustomBtn({Key? key, this.title, this.callback}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.only(bottom: 10),
      child: SizedBox(
        width: double.infinity,
        child: Container(
          color: Colors.blue,
          child: TextButton(
            onPressed: () => callback!(),
            child: title!,
          ),
        ),
      ),
    );
  }
}

为我们的表格创建步骤

为了使用我们的Stepper小组件,我们将从创建一个步骤列表开始。

因为我们已经定义了步进部件的一些重要功能,所以我们在这里就不多说了。我们可以直接跳入。

List<Step> getSteps() {
  return <Step>[
    Step(
      state: currentStep > 0 ? StepState.complete : StepState.indexed,
      isActive: currentStep >= 0,
      title: const Text("Account Info"),
      content: Column(
        children: const [
          CustomInput(
            hint: "First Name",
            inputBorder: OutlineInputBorder(),
          ),
          CustomInput(
            hint: "Last Name",
            inputBorder: OutlineInputBorder(),
          ),
        ],
      ),
    ),
    Step(
      state: currentStep > 1 ? StepState.complete : StepState.indexed,
      isActive: currentStep >= 1,
      title: const Text("Address"),
      content: Column(
        children: const [
          CustomInput(
            hint: "City and State",
            inputBorder: OutlineInputBorder(),
          ),
          CustomInput(
            hint: "Postal Code",
            inputBorder: OutlineInputBorder(),
          ),
        ],
      ),
    ),
    Step(
      state: currentStep > 2 ? StepState.complete : StepState.indexed,
      isActive: currentStep >= 2,
      title: const Text("Misc"),
      content: Column(
        children: const [
          CustomInput(
            hint: "Bio",
            inputBorder: OutlineInputBorder(),
          ),
        ],
      ),
    ),
  ];
}

在创建了我们的步进小部件所需的步骤后,我们现在可以在我们的formpage.dart 中替换ListView 。但在此之前,让我们看看我们的单步的每个字段代表什么。

我们对单步的第一个属性是state :你可能记得,这定义了步进器上的领先图标,如下图所示。当用户完成了对某一步骤的字段的编辑并移动到下一步骤时,前一步骤在state 属性中被标记为已完成,而当前步骤被标记为indexed ,这仅仅意味着用户正在积极编辑这一步骤。

Complete Indexed

isActive 属性只是用来显示用户目前正在查看哪一个步骤。title 收录了一个小部件,并将其显示在每个步骤的顶部,而每个步骤的内容是我们希望用户与之互动的实际表单小部件。

在用Stepper 替换了我们之前的Listview 小部件后,我们的代码看起来是这样的。

import 'package:flutter/material.dart';
import 'package:stepper_widget/widgets/custom_input.dart';

class FormPage extends StatefulWidget {
  const FormPage({Key? key}) : super(key: key);

  @override
  _FormPageState createState() => _FormPageState();
}

class _FormPageState extends State<FormPage> {
  int currentStep = 0;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text(
            "Stepper Widget ",
          ),
          centerTitle: true,
        ),
        body: Container(
            padding: const EdgeInsets.all(20),
            child: Stepper(
              type: StepperType.horizontal,
              currentStep: currentStep,
              onStepCancel: () => currentStep == 0
                  ? null
                  : setState(() {
                      currentStep -= 1;
                    }),
              onStepContinue: () {
                bool isLastStep = (currentStep == getSteps().length - 1);
                if (isLastStep) {
                  //Do something with this information
                } else {
                  setState(() {
                    currentStep += 1;
                  });
                }
              },
              onStepTapped: (step) => setState(() {
                currentStep = step;
              }),
              steps: getSteps(),
            )),
      ),
    );
  }

  List<Step> getSteps() {
    return <Step>[
      Step(
        state: currentStep > 0 ? StepState.complete : StepState.indexed,
        isActive: currentStep >= 0,
        title: const Text("Account Info"),
        content: Column(
          children: const [
            CustomInput(
              hint: "First Name",
              inputBorder: OutlineInputBorder(),
            ),
            CustomInput(
              hint: "Last Name",
              inputBorder: OutlineInputBorder(),
            ),
          ],
        ),
      ),
      Step(
        state: currentStep > 1 ? StepState.complete : StepState.indexed,
        isActive: currentStep >= 1,
        title: const Text("Address"),
        content: Column(
          children: const [
            CustomInput(
              hint: "City and State",
              inputBorder: OutlineInputBorder(),
            ),
            CustomInput(
              hint: "Postal Code",
              inputBorder: OutlineInputBorder(),
            ),
          ],
        ),
      ),
      Step(
        state: currentStep > 2 ? StepState.complete : StepState.indexed,
        isActive: currentStep >= 2,
        title: const Text("Misc"),
        content: Column(
          children: const [
            CustomInput(
              hint: "Bio",
              inputBorder: OutlineInputBorder(),
            ),
          ],
        ),
      ),
    ];
  }
}

了解阶梯小部件

现在让我们来看看我们在Stepper小组件中定义的每个属性。

type 开始,我们定义了我们的步进小部件的内容应该如何在步进器内布局。对于垂直和水平的步进类型,我们的步进小部件会是这样的。

Account Info Fields
Account Info Fields Type 2

CurrentStep 简单地接收用户当前可见的步骤的索引值。

OnStepCancel 是当我们的表单的用户点击后退按钮时实施的回调,我们目前正在做一个检查,以防止按钮在第一步就被激活。

onStepContinue 是我们的继续按钮的回调。在这里我们也有一个检查,以了解用户在最后一步的情况,在这里我们可以使用提供给我们的信息进行必要的操作。

OnStepTapped 返回用户点击的步骤,我们可以通过将其设置为当前步骤的值来使其激活。

其他可以为我们的Stepper部件提供更多的自定义功能,包括添加一个自定义主题或实现我们自己的自定义控制按钮,也就是我们目前的NextContinue按钮。要做到这一点,只需使用我们的Stepper widget的theme 属性和controlsBuilder 属性。

最后的想法

使用多步骤表单可以大大改善用户体验,增加我们设计的视觉吸引力。

Flutter中的Stepper小组件在需要的情况下,为我们提供了很多有用的自定义功能,在不依赖第三方库的情况下获得我们想要的结果。