Flutter动画导航栏插件animated_wizard_bar的使用
Flutter动画导航栏插件animated_wizard_bar的使用
简介
animated_wizard_bar
是一个高度可定制的 Flutter 插件,用于创建向导式的多步骤用户界面。通过平滑的动画、动态的步骤指示器和直观的导航控件,这个插件简化了创建交互式表单、引导流程或任何多步骤 UI 的过程。
功能特性
- 带有页面视图的动画向导栏:支持与
PageView
集成,实现流畅的页面切换。 - 自定义颜色:可以为选中和未选中的图标设置不同的颜色。
- 自定义指示器和项目装饰:可以根据需求自定义指示器和项目的样式。
- 动画步骤指示器:支持自定义图标和样式的动画效果。
- 水平滚动的向导栏:支持水平滚动,适用于多个步骤的场景。
- 灵活的 API:提供灵活的 API 用于控制步骤之间的导航。
- 动态内容支持:每个步骤可以包含动态内容。
- 内置动画:提供过渡和缩放效果的内置动画。
- 轻量级且易于集成:体积小,易于集成到任何 Flutter 项目中。
安装
在 pubspec.yaml
文件中添加以下依赖项:
dependencies:
animated_wizard_bar: <latest_version>
使用示例
1. 主应用程序入口
首先,在 main.dart
文件中配置 MultiProvider
和 MyApp
:
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. 创建具体页面
为了使向导更加完整,我们可以创建一些具体的页面,例如 IdentityScreen
、Residential
和 Persons
。
// 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
更多关于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')),
],
),
),
],
),
);
}
}
在这个示例中:
- 我们创建了一个包含三个步骤的
AnimatedWizardBar
。 - 使用
TabController
来管理步骤之间的切换,并更新AnimatedWizardBar
的当前索引。 AnimatedWizardBar
的onStepClick
回调用于处理步骤点击事件,从而更新TabController
的索引。TabBarView
用于显示每个步骤的内容。
你可以根据需要自定义AnimatedWizardBar
的参数,如步骤数量、颜色、动画持续时间等。希望这个示例能帮助你更好地理解和使用animated_wizard_bar
插件。