Flutter动画导航栏插件animated_wizard_bar的使用

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

Flutter动画导航栏插件animated_wizard_bar的使用

简介

animated_wizard_bar 是一个高度可定制的 Flutter 插件,用于创建向导式的多步骤用户界面。通过平滑的动画、动态的步骤指示器和直观的导航控件,这个插件简化了创建交互式表单、引导流程或任何多步骤 UI 的过程。

功能特性

  • 带有页面视图的动画向导栏:支持与 PageView 集成,实现流畅的页面切换。
  • 自定义颜色:可以为选中和未选中的图标设置不同的颜色。
  • 自定义指示器和项目装饰:可以根据需求自定义指示器和项目的样式。
  • 动画步骤指示器:支持自定义图标和样式的动画效果。
  • 水平滚动的向导栏:支持水平滚动,适用于多个步骤的场景。
  • 灵活的 API:提供灵活的 API 用于控制步骤之间的导航。
  • 动态内容支持:每个步骤可以包含动态内容。
  • 内置动画:提供过渡和缩放效果的内置动画。
  • 轻量级且易于集成:体积小,易于集成到任何 Flutter 项目中。

安装

pubspec.yaml 文件中添加以下依赖项:

dependencies:
  animated_wizard_bar: <latest_version>

使用示例

1. 主应用程序入口

首先,在 main.dart 文件中配置 MultiProviderMyApp

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:animated_wizard_bar/page_view/custom_page_viewmodel.dart';
import 'package:animated_wizard_bar/page_view/wizrdbar_viewmodel.dart';
import 'package:animated_wizard_bar/page_view/widgets/custom_page_view_package.dart';
import 'package:animated_wizard_bar/page_view/widgets/step_horizontal_animation.dart';
import 'package:animated_wizard_bar/page_view/widgets/wizardbar_animation.dart';
import 'package:flutter_tabler_icons/flutter_tabler_icons.dart';

void main() {
  runApp(multiProvider);
}

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const ExampleWizard(),
    );
  }
}

List<SingleChildWidget> providers = [
  ChangeNotifierProvider<CustomPageViewModel>(
    create: (context) => CustomPageViewModel(),
  ),
  ChangeNotifierProvider<WizardBarViewModel>(
    create: (context) => WizardBarViewModel(),
  ),
];
MultiProvider multiProvider = MultiProvider(providers: providers, child: const MyApp());
2. 创建向导页面

接下来,创建一个 ExampleWizard 类来实现向导页面:

class ExampleWizard extends StatefulWidget {
  const ExampleWizard({super.key});

  [@override](/user/override)
  State<ExampleWizard> createState() => _ExampleWizardState();
}

class _ExampleWizardState extends State<ExampleWizard> with TickerProviderStateMixin {
  final ScrollController singleChildScrollControllerWizardBar = ScrollController();

  var key1 = GlobalKey();
  var key2 = GlobalKey();
  var key3 = GlobalKey();
  var key4 = GlobalKey();
  var key5 = GlobalKey();

  List<Animation<double>> animationList = [];
  List<AnimationController> aniControllerList = [];
  List<Widget> pageViewList = [
    Container(
      color: Colors.amber,
      child: const Center(
        child: Text('0'),
      ),
    ),
    const Center(
      child: Text('1'),
    ),
    const Center(
      child: Text('2'),
    ),
    const Center(
      child: Text('3'),
    ),
    const Center(
      child: Text('4'),
    ),
  ];

  [@override](/user/override)
  void initState() {
    // 初始化动画控制器
    aniControllerList = List.generate(
      5,
      (_) => AnimationController(
        vsync: this,
        duration: const Duration(milliseconds: 800),
        reverseDuration: const Duration(milliseconds: 800),
      )..addListener(() {}),
    );

    // 创建缩放动画
    animationList = aniControllerList.asMap().entries.map((entry) {
      int index = entry.key;
      AnimationController controller = entry.value;
      if (index == 0) {
        return Tween<double>(begin: 0.95, end: 1.35).animate(controller);
      } else {
        return Tween<double>(begin: 1.35, end: 0.95).animate(controller);
      }
    }).toList();

    // 启动所有动画
    for (var controller in aniControllerList) {
      controller.forward();
    }

    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    List<StepHorizontalAnimation> stepsList = [
      StepHorizontalAnimation(
        filled: false,
        boxKey: key1,
        icon: TablerIcons.user,
        visibleLeft: true,
        itemsNeedForFilled: 6,
        stepsNumber: 0,
        scaleAnimation: animationList[0],
        scaleAnimationList: aniControllerList,
        scrollController: singleChildScrollControllerWizardBar,
        canSelect: false,
      ),
      StepHorizontalAnimation(
        filled: false,
        boxKey: key2,
        icon: TablerIcons.home_check,
        visibleLeft: true,
        itemsNeedForFilled: 3,
        stepsNumber: 1,
        scaleAnimation: animationList[1],
        scaleAnimationList: aniControllerList,
        scrollController: singleChildScrollControllerWizardBar,
        canSelect: true,
      ),
      StepHorizontalAnimation(
        filled: false,
        boxKey: key3,
        icon: TablerIcons.briefcase,
        visibleLeft: true,
        itemsNeedForFilled: 2,
        stepsNumber: 2,
        scaleAnimation: animationList[2],
        scaleAnimationList: aniControllerList,
        scrollController: singleChildScrollControllerWizardBar,
        canSelect: true,
      ),
      StepHorizontalAnimation(
        filled: true,
        boxKey: key4,
        icon: TablerIcons.users,
        visibleLeft: true,
        itemsNeedForFilled: 13,
        stepsNumber: 3,
        scaleAnimation: animationList[3],
        scaleAnimationList: aniControllerList,
        scrollController: singleChildScrollControllerWizardBar,
        canSelect: true,
      ),
      StepHorizontalAnimation(
        filled: true,
        boxKey: key5,
        icon: TablerIcons.building_store,
        visibleLeft: false,
        itemsNeedForFilled: 1,
        stepsNumber: 4,
        scaleAnimation: animationList[4],
        scaleAnimationList: aniControllerList,
        scrollController: singleChildScrollControllerWizardBar,
        canSelect: true,
      ),
    ];

    final getCustomPageViewModel = Provider.of<CustomPageViewModel>(context, listen: false);

    return WillPopScope(
      onWillPop: () async {
        if (getCustomPageViewModel.currentLevel == 0) {
          return true;
        } else {
          getCustomPageViewModel.previousPage(aniControllerList, singleChildScrollControllerWizardBar, stepsList.length, stepsList[getCustomPageViewModel.currentLevel].boxKey);
          return false;
        }
      },
      child: SafeArea(
        child: Scaffold(
          body: CustomPageView(
            appBar: AppBar(
              title: const Text(
                'animated wizard bar',
                style: TextStyle(color: Colors.black),
              ),
              centerTitle: true,
            ),
            pageViewItems: pageViewList,
            aniController: aniControllerList,
            stepsList: stepsList,
            singleChildScrollController: singleChildScrollControllerWizardBar,
            wizardBarAnimation: WizardBarAnimation(singleChildScrollControllerWizardBar, stepsList),
          ),
        ),
      ),
    );
  }
}
3. 创建具体页面

为了使向导更加完整,我们可以创建一些具体的页面,例如 IdentityScreenResidentialPersons

// IdentityScreen 页面
class IdentityScreen extends StatefulWidget {
  const IdentityScreen({super.key});

  [@override](/user/override)
  State<IdentityScreen> createState() => _IdentityScreenState();
}

class _IdentityScreenState extends State<IdentityScreen> {
  final formKeyName = GlobalKey<FormState>();
  final formKeyLastName = GlobalKey<FormState>();
  final formKeyNationalId = GlobalKey<FormState>();
  final formKeyPhoneNumber = GlobalKey<FormState>();
  final formKeyFatherName = GlobalKey<FormState>();

  final FocusNode focusNodeName = FocusNode();
  final FocusNode focusNodeLastName = FocusNode();
  final FocusNode focusNodeNationalId = FocusNode();
  final FocusNode focusNodePhoneNumber = FocusNode();
  final FocusNode focusNodeFatherName = FocusNode();

  TextEditingController nameValue = TextEditingController();
  TextEditingController lastNameValue = TextEditingController();
  TextEditingController nationalIdValue = TextEditingController();
  TextEditingController phoneNumberValue = TextEditingController();
  TextEditingController fatherNameValue = TextEditingController();

  final List<String> items = ['', 'first', 'second'];

  String lastTextOfName = '';
  String lastTextOFLastName = '';
  String lastTextOFNationalId = '';
  String lastTextOFFatherName = '';
  String lastTextOFPhoneNumber = '';

  [@override](/user/override)
  void initState() {
    lastTextOfName = '';
    lastTextOFLastName = '';
    lastTextOFNationalId = '';
    lastTextOFFatherName = '';
    lastTextOFPhoneNumber = '';

    nameValue.text = '';
    lastNameValue.text = '';
    nationalIdValue.text = '';
    phoneNumberValue.text = '';
    fatherNameValue.text = '';
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24.0),
      child: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                '填写必填信息',
                style: TextStyle(fontSize: 16),
              ),
              WizardTextField(
                  args: WizardTextFieldArguments(
                keyTextField: formKeyName,
                focusNode: focusNodeName,
                textEditingController: nameValue,
                label: '姓名',
                enable: true,
                valueChanged: () {
                  lastTextOfName = nameValue.text;
                },
              )),
              WizardTextField(
                  args: WizardTextFieldArguments(
                keyTextField: formKeyLastName,
                focusNode: focusNodeLastName,
                textEditingController: lastNameValue,
                label: '姓氏',
                enable: true,
                valueChanged: () {
                  lastTextOFLastName = lastNameValue.text;
                },
              )),
              WizardTextField(
                  args: WizardTextFieldArguments(
                keyTextField: formKeyNationalId,
                focusNode: focusNodeNationalId,
                textEditingController: nationalIdValue,
                label: '身份证号',
                enable: true,
                valueChanged: () {
                  lastTextOFNationalId = nationalIdValue.text;
                },
              )),
              WizardTextField(
                  args: WizardTextFieldArguments(
                keyTextField: formKeyFatherName,
                focusNode: focusNodeFatherName,
                textEditingController: fatherNameValue,
                label: '父亲姓名',
                enable: true,
                valueChanged: () {
                  lastTextOFFatherName = fatherNameValue.text;
                },
              )),
              WizardTextField(
                  args: WizardTextFieldArguments(
                keyTextField: formKeyPhoneNumber,
                focusNode: focusNodePhoneNumber,
                textEditingController: phoneNumberValue,
                label: '电话号码',
                enable: true,
                valueChanged: () {
                  lastTextOFPhoneNumber = phoneNumberValue.text;
                },
              )),
            ],
          ),
        ),
      ),
    );
  }
}

// Residential 页面
class Residential extends StatefulWidget {
  const Residential({super.key});

  [@override](/user/override)
  State<Residential> createState() => _ResidentialState();
}

class _ResidentialState extends State<Residential> {
  List<String> continents = [
    '',
    'Europe',
    'Asia',
    'Africa',
    'North America',
    'South America',
    'Oceania',
    'Antarctica',
  ];

  List<String> ageRange = [
    '',
    '15 - 20',
    '20 - 25',
    '25 - 30',
    '30 - 35',
  ];

  String lastTextOfContinents = '';
  String lastTextOfAgeRange = '';

  [@override](/user/override)
  void initState() {
    lastTextOfContinents = '';
    lastTextOfAgeRange = '';
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24.0),
      child: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                '你来自哪个大洲?',
                textDirection: TextDirection.ltr,
              ),
              DropdownButtonFormField<String>(
                decoration: InputDecoration(border: OutlineInputBorder(borderRadius: BorderRadius.circular(14))),
                items: continents
                    .map((i) => DropdownMenuItem(
                        value: i,
                        child: Container(
                            alignment: Alignment.centerRight,
                            child: Text(
                              i,
                              style: const TextStyle(
                                color: Colors.black,
                                fontFamily: "IranYekan",
                              ),
                            ))))
                    .toList(),
                onChanged: (value) {
                  if (value == '' && lastTextOfContinents != '') {
                    final getWizardPageViewModel = Provider.of<WizardBarViewModel>(context, listen: false);
                    getWizardPageViewModel.decreaseContainerSize();
                  } else if (value != '' && lastTextOfContinents == '') {
                    final getWizardPageViewModel = Provider.of<WizardBarViewModel>(context, listen: false);
                    getWizardPageViewModel.increaseContainerSize();
                  }
                  lastTextOfContinents = value ?? '';
                },
              ),
              const Text(
                '你的年龄范围是?',
                textDirection: TextDirection.ltr,
              ),
              DropdownButtonFormField<String>(
                decoration: InputDecoration(border: OutlineInputBorder(borderRadius: BorderRadius.circular(14))),
                items: ageRange
                    .map((i) => DropdownMenuItem(
                        value: i,
                        child: Container(
                            alignment: Alignment.centerRight,
                            child: Text(
                              i,
                              style: const TextStyle(
                                color: Colors.black,
                                fontFamily: "IranYekan",
                              ),
                            ))))
                    .toList(),
                onChanged: (value) {
                  if (value == '' && lastTextOfAgeRange != '') {
                    final getWizardPageViewModel = Provider.of<WizardBarViewModel>(context, listen: false);
                    getWizardPageViewModel.decreaseContainerSize();
                  } else if (value != '' && lastTextOfAgeRange == '') {
                    final getWizardPageViewModel = Provider.of<WizardBarViewModel>(context, listen: false);
                    getWizardPageViewModel.increaseContainerSize();
                  }
                  lastTextOfAgeRange = value ?? '';
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// Persons 页面
class Persons extends StatefulWidget {
  const Persons({super.key});

  [@override](/user/override)
  State<Persons> createState() => _PersonsState();
}

class _PersonsState extends State<Persons> {
  int radioValue = 0;
  String _selectedValue = ''; // 当前选中的值
  final List<String> _options = ['选项 1', '选项 2'];

  [@override](/user/override)
  void initState() {
    _selectedValue = '';
    super.initState();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(24.0),
      child: SingleChildScrollView(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                '请选择一个选项?',
                textDirection: TextDirection.ltr,
              ),
              Center(
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: _options.map((option) {
                    return Row(
                      children: [
                        Radio<String>(
                          value: option,
                          groupValue: _selectedValue,
                          onChanged: (value) {
                            if (value == '' && _selectedValue != '') {
                              final getWizardPageViewModel = Provider.of<WizardBarViewModel>(context, listen: false);
                              getWizardPageViewModel.decreaseContainerSize();
                            } else if (value != '' && _selectedValue == '') {
                              final getWizardPageViewModel = Provider.of<WizardBarViewModel>(context, listen: false);
                              getWizardPageViewModel.increaseContainerSize();
                            }
                            setState(() {
                              _selectedValue = value ?? '';
                            });
                          },
                        ),
                        Text(option),
                      ],
                    );
                  }).toList(),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

更多关于Flutter动画导航栏插件animated_wizard_bar的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter动画导航栏插件animated_wizard_bar的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用animated_wizard_bar插件的一个基本示例。这个插件通常用于创建具有动画效果的导航栏,非常适合向导或步骤指示器场景。

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

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

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

以下是一个简单的使用animated_wizard_bar的示例代码:

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

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

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

class WizardBarDemo extends StatefulWidget {
  @override
  _WizardBarDemoState createState() => _WizardBarDemoState();
}

class _WizardBarDemoState extends State<WizardBarDemo> with SingleTickerProviderStateMixin {
  late TabController _tabController;
  int _currentIndex = 0;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this, initialIndex: 0);
    _tabController.addListener(() {
      setState(() {
        _currentIndex = _tabController.index;
      });
    });
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Animated Wizard Bar Demo'),
      ),
      body: Column(
        children: <Widget>[
          AnimatedWizardBar(
            steps: 3,
            currentIndex: _currentIndex,
            onStepClick: (index) {
              _tabController.index = index;
            },
            barColor: Colors.blue,
            dotColor: Colors.white,
            dotActiveColor: Colors.blue,
            dotSize: 14,
            dotSpacing: 16,
            lineColor: Colors.blue.withOpacity(0.3),
            lineActiveColor: Colors.blue,
            lineThickness: 4,
            animateDuration: Duration(milliseconds: 300),
          ),
          Expanded(
            child: TabBarView(
              controller: _tabController,
              children: [
                Center(child: Text('Step 1')),
                Center(child: Text('Step 2')),
                Center(child: Text('Step 3')),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

在这个示例中:

  1. 我们创建了一个包含三个步骤的AnimatedWizardBar
  2. 使用TabController来管理步骤之间的切换,并更新AnimatedWizardBar的当前索引。
  3. AnimatedWizardBaronStepClick回调用于处理步骤点击事件,从而更新TabController的索引。
  4. TabBarView用于显示每个步骤的内容。

你可以根据需要自定义AnimatedWizardBar的参数,如步骤数量、颜色、动画持续时间等。希望这个示例能帮助你更好地理解和使用animated_wizard_bar插件。

回到顶部