6. 表单
6.1. Switch
Switchvalue(开关的值,一般与状态字段绑定)onChanged(开关状态变更时调用)activeColor(开关开启时的圆圈颜色)activeTrackColor(开关开启时的轨道颜色)inactiveThumbColor(开关关闭时的圆圈颜色)inactiveTrackColor(开关关闭时的轨道颜色)
CupertinoSwitch(ios 风格的开关)import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 头部导航条
appBar: AppBar(
// 标题
title: const Text('Switch'),
// 左侧插槽
leading: const Icon(Icons.menu),
// 右侧插槽
actions: const [
Icon(Icons.add_circle_outline),
],
// 设置下边阴影
elevation: 0.0,
// 设置标题居中
centerTitle: true,
),
body: const SwitchDemo(),
);
}
}
class SwitchDemo extends StatefulWidget {
const SwitchDemo({super.key});
@override
State<SwitchDemo> createState() => _SwitchDemoState();
}
class _SwitchDemoState extends State<SwitchDemo> {
bool _switchValue = false;
@override
Widget build(BuildContext context) {
return Container(
child: ListView(
children: [
ListTile(
leading: Switch(
value: _switchValue,
activeColor: Colors.orange,
activeTrackColor: Colors.pink,
inactiveThumbColor: Colors.blue[100],
inactiveTrackColor: Colors.grey,
onChanged: (bool value) {
setState(() {
_switchValue = value;
});
},
),
title: Text('当前的状态:${_switchValue ? '选中' : '未选中'}'),
),
ListTile(
leading: CupertinoSwitch(
value: _switchValue,
activeColor: Colors.red,
trackColor: Colors.yellow,
onChanged: (bool value) {
setState(() {
_switchValue = value;
});
},
),
title: const Text('IOS风格的Switch'),
),
],
),
);
}
}
6.2. Checkbox
Checkboxvalue(复选框的值)onChanged(复选框状态更改时调用)activeColor(选中时,复选框背景的颜色)checkColor(选中时,复选框中对号的颜色)
CheckboxListTiletitle(标题)subtitle(子标题)
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 头部导航条
appBar: AppBar(
// 标题
title: const Text('CheckBox'),
// 左侧插槽
leading: const Icon(Icons.menu),
// 右侧插槽
actions: const [
Icon(Icons.add_circle_outline),
],
// 设置下边阴影
elevation: 0.0,
// 设置标题居中
centerTitle: true,
),
body: const CheckBoxDemo(),
);
}
}
class CheckBoxDemo extends StatefulWidget {
const CheckBoxDemo({super.key});
@override
State<CheckBoxDemo> createState() => _CheckBoxDemoState();
}
class _CheckBoxDemoState extends State<CheckBoxDemo> {
bool _male = true;
bool _female = false;
bool _transgender = true;
bool _value1 = true;
bool _value2 = false;
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
title: const Text('男'),
leading: Checkbox(
value: _male,
onChanged: (value) {
setState(() {
_male = value!;
});
},
),
),
ListTile(
title: const Text('女'),
leading: Checkbox(
value: _female,
onChanged: (value) {
setState(() {
_female = value!;
});
},
),
),
ListTile(
title: const Text('人妖'),
leading: Checkbox(
value: _transgender,
activeColor: Colors.red,
checkColor: Colors.green,
onChanged: (value) {
setState(() {
_transgender = value!;
});
},
),
),
CheckboxListTile(
title: const Text('1:00 叫我起床'),
subtitle: const Text('太困了,起不来'),
secondary: const Icon(
Icons.settings,
size: 50,
),
activeColor: Colors.red,
checkColor: Colors.blue,
selected: _value1,
value: _value1,
onChanged: (value) {
setState(() {
_value1 = value!;
});
},
),
CheckboxListTile(
title: const Text('2:00 叫我起床'),
subtitle: const Text('这还差不多'),
secondary: const Icon(
Icons.settings,
size: 50,
),
value: _value2,
onChanged: (value) {
setState(() {
_value2 = value!;
});
},
),
],
);
}
}
6.3. Radio
Radio(单选框)value(开关的值,一般与状态字段绑定)onChanged(开关状态变更时调用)groupValue(选择组的值)
RadioListTile(单选列表)value(开关的值,一般与状态字段绑定)onChanged(开关状态变更时调用)groupValue(选择组的值)
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 头部导航条
appBar: AppBar(
// 标题
title: const Text('Radio'),
// 左侧插槽
leading: const Icon(Icons.menu),
// 右侧插槽
actions: const [
Icon(Icons.add_circle_outline),
],
// 设置下边阴影
elevation: 0.0,
// 设置标题居中
centerTitle: true,
),
body: const RadioDemo(),
);
}
}
class RadioDemo extends StatefulWidget {
const RadioDemo({super.key});
@override
State<RadioDemo> createState() => _RadioDemoState();
}
class _RadioDemoState extends State<RadioDemo> {
int gender = 1;
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('男'),
Radio(
value: 1,
groupValue: gender,
onChanged: (value) {
setState(() {
gender = value!;
});
},
),
const Text('女'),
Radio(
value: 2,
groupValue: gender,
onChanged: (value) {
setState(() {
gender = value!;
});
},
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(gender == 1 ? '男' : '女'),
],
),
RadioListTile(
title: const Text('男性'),
subtitle: const Text('有胡子'),
selected: gender == 1,
selectedTileColor: const Color.fromARGB(255, 240, 213, 137),
secondary: const Icon(
Icons.man,
size: 32,
),
value: 1,
groupValue: gender,
onChanged: (value) {
setState(() {
gender = value!;
});
},
),
RadioListTile(
title: const Text('女性'),
subtitle: const Text('没有胡子'),
selected: gender == 2,
selectedTileColor: const Color.fromARGB(255, 240, 213, 137),
secondary: const Icon(
Icons.woman,
size: 32,
),
value: 2,
groupValue: gender,
onChanged: (value) {
setState(() {
gender = value!;
});
},
),
],
),
);
}
}
6.4. TextField
autofocus(是否获取焦点)keyboardType(键盘类型)obscureText(设置为密码框)decoration(样式修饰)onChanged(内容更改时自动调用 -value)labelText(标题)hintText(提示文字 -placeholder)maxLines(显示行数-文本域)
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 头部导航条
appBar: AppBar(
// 标题
title: const Text('TextField'),
// 左侧插槽
leading: const Icon(Icons.menu),
// 右侧插槽
actions: const [
Icon(Icons.add_circle_outline),
],
// 设置下边阴影
elevation: 0.0,
// 设置标题居中
centerTitle: true,
),
body: const TextFieldDemo(),
);
}
}
class TextFieldDemo extends StatefulWidget {
const TextFieldDemo({super.key});
@override
State<TextFieldDemo> createState() => _TextFieldDemoState();
}
class _TextFieldDemoState extends State<TextFieldDemo> {
late String phone;
late String password;
late String description;
_register() {
debugPrint(phone);
debugPrint(password);
debugPrint(description);
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
TextField(
autofocus: true,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.mobile_screen_share),
labelText: '手机号',
hintText: '请输入手机号',
hintStyle: TextStyle(
color: Colors.green,
fontSize: 14,
),
// 获取焦点时,高亮的边框样式
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.green,
),
),
// 默认边框样式
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.yellow,
),
),
),
maxLength: 11,
onChanged: (value) {
setState(() {
phone = value!;
});
},
),
TextField(
// 不可见
obscureText: true,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.remove_red_eye),
labelText: '密码',
hintText: '请输入密码',
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
password = value!;
});
},
),
TextField(
maxLines: 5,
keyboardType: TextInputType.text,
decoration: const InputDecoration(
prefixIcon: Icon(Icons.person),
labelText: '简介',
hintText: '请介绍一下自己',
),
onChanged: (value) {
setState(() {
description = value!;
});
},
),
// 提交按钮
Container(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
_register();
},
child: const Text('提交'),
),
),
],
),
);
}
}
6.5. 日历
CalendarDatePicker(日历选择器)initialCalendarModeDatePickerMode.dayDatePickerMode.year
showDatePicker(日期选择器)initialDatePickerMode(year|day)initialEntryMode(calendar|input)
showTimePicker(时间选择器)
6.6. Form
-
使用步骤
- 创建表单
Form,并以GlobalKey作为唯一性标识 - 添加带验证逻辑的
TextFormField到Form中 - 创建按钮以验证和提交表单
- 创建表单
-
Form(表单容器)key(GlobalKey)child(子组件)
-
TextFormField(输入框)- 与
TextField的区别: 必须在Form内使用 & 带有验证器 validator(验证器)
- 与
-
Form(表单容器)- 创建表单唯一键:
final GlobalKey<FormState> formKey = GlobalKey<FormState>(); - 验证表单:
_formKey.currentState.validate() - 提交表单:
_formKey.currentState.save[) - 重置表单:
_formKey.currentState.reset()
- 创建表单唯一键:
-
TextFormField(输入框)validator(验证器)obscureText(密码框)onSaved- 设定表单字段的值
- 在表单的
save()方法之后执行
import 'package:flutter/material.dart';
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 头部导航条
appBar: AppBar(
// 标题
title: const Text('Form'),
// 左侧插槽
leading: const Icon(Icons.menu),
// 右侧插槽
actions: const [
Icon(Icons.add_circle_outline),
],
// 设置下边阴影
elevation: 0.0,
// 设置标题居中
centerTitle: true,
),
body: const FormDemo(),
);
}
}
class FormDemo extends StatefulWidget {
const FormDemo({super.key});
@override
State<FormDemo> createState() => _FormDemoState();
}
class _FormDemoState extends State<FormDemo> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late String _phone;
late String _password;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.all(20),
child: Column(
children: [
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
hintText: '请输入手机号',
),
validator: (value) {
RegExp reg = RegExp(r'^1\d{10}$');
if (!reg.hasMatch(value!)) {
return '请输入正确的手机号';
}
return null;
},
onSaved: (value) {
debugPrint('_phone onSaved');
_phone = value!;
},
),
TextFormField(
obscureText: true,
decoration: const InputDecoration(
hintText: '请输入密码',
),
validator: (value) {
return value!.length < 6 ? '密码长度不合法' : null;
},
onSaved: (value) {
debugPrint('_password onSaved');
_password = value!;
},
),
],
),
),
Row(
children: [
Expanded(
child: ElevatedButton(
child: const Text('提交'),
onPressed: () {
if (_formKey.currentState!.validate()) {
debugPrint('验证通过');
// 提交表单
debugPrint('_formKey.currentState!.save() - Before');
_formKey.currentState!.save();
debugPrint('_formKey.currentState!.save() - After');
debugPrint(_phone);
debugPrint(_password);
}
},
),
),
const SizedBox(
width: 20,
),
Expanded(
child: ElevatedButton(
onPressed: () {
_formKey.currentState!.reset();
},
child: const Text('重置'),
),
),
],
),
],
),
);
}
}
7. 其他
7.1. 动画
- Why
- ui 界面设计合理的动画,可以让用户觉得更加流畅、直观,可以极大提高和改善用户体验。
- What (实现原理)
- 动画就是动起来的画面
- 视觉暂留: 画面经视神经传入大脑后,不会立即消失(会存留一段时间)
- 帧 (Frame) : 单个的画面,在学术上叫帧
- 每秒中展示的帧数简称 fps (Frame per Second)
7.1.1. 动画的分类
-
补间 (Tween)动画
- 在补间动画中我们定义开始点和结束点、时间线以及定义转换时间和速度曲线。然后由系统计算,从开始点运动到结束点。从而形成动画效果。
- 例如: 透明度从 0到 1,颜色值从 到 255
-
拟物动画
- 拟物动画是对真实世界的行为进行建模,,使动画效果类似于现实中的物理效果。
- 例如: 弹簧,阻尼,重力,抛物线等
-
动画 - Animation
-
Animation,是Flutter 动画库中的一个核心类。它包含动画的值和状态两个属性,定义了动画的一系列监听函数
- 监听值:
addListenerremoveListener
- 监听状态:
addStatusListenerremoveStatusListener
- 监听值:
-
动画状态
AnimationStatus.dismissed- 动画初始状态
AnimationStatus.completed- 动画结束状态
AnimationStatus.forward- 动画处在从开始到结束的运行状态
AnimationStatus.reverse- 动画处在从结束到开始的运行状态
-
AnimationController(动画控制器)- 在指定时间内,将组件属性值由初始值演变到终止值。从而形成动画效果
-
AnimationController参数duration(动画的执行时间)reverseDuration(动画反向执行时间)lowerBound = 0.0(动画最小值)upperBound = 1.0(动画最大值)value(动画初始值,默认是lowerBound)vsync(TickerProvider类型的对象,用来创建Ticker对象)
-
当创建一个 AnimationController 时,需要传递一个
vsync参数vsync的作用是: 防止屏幕外动画 (动画页面切换到后台时)消耗不必要的资源- 通过将
SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
- AnimationController 具有控制动画的方法
forward()可以正向执行动画reverse()可以反向执行动画dispose()用来释放动画资源(在不使用时需要调用该方法,否则会造成资源泄露)stop()用来停止动画运行
7.1.2. 动画 - Tween
- 简介
AnimationController动画生成值的默认区间是0.0到1.0,如果希望使用不同的区间,或不同的数据类型,需要使用 Tween (补间动画)。- Tween 的唯一职责就是定义从输入范围到输出范围的映射
- 例如: 颜色区间是0到 255
TweenTween<double>(begin: 起始值, end: 终止值);ColorTween(begin: Colors.withe, end: Colors.black);
7.1.3. 动画 - CurvedAnimation
- 简介
- 动画执行的速度有多种 (匀速、先快后慢或先慢后快) 这里的速度称为动画曲线
CurvedAnimation的目的是为AnimationController添加动画曲线
- 组件
CurvedAnimation(parent: controller, curve: Curves.easeln)parent(动画控制器对象)curve(正向执行的动画曲线)reverseCurve(反向执行的动画曲线
- Curves
7.1.4. 动画-步骤
- 创建动画控制器
controller = AnimationController(duration, vsync)
- 创建动画
- 动画曲线 (CurvedAnimation)
- 补间动画(Tween )
- 监听动画
addListener()// 监听动画生成值addStatusListener()// 监听动画状态
- 执行动画
controller.forward()// 正向执行controllerreverse()// 反向执行