# 基础
# 初始化项目
# 项目目录
# UI 库
Material Design(google):
- 中文文档
- 英文文档 (opens new window)
import 'package:flutter/material.dart';
# APP 结构
flutter 一切都是组件(Widget 类),组件嵌套组件,组件组成页面,页面组成 APP。
MaterialApp(顶级组件)
- title(任务管理器中的标题)
- home(主内容)
- debugShowCheckedModeBanner(是否显示右上角的 debug 标签)
Scaffold(脚手架组件)
- appBar(顶部导航栏)
- body(主内容)
- floatingActionButton(悬浮按钮)
- drawer(左侧抽屉菜单)
- endDrawer(右侧抽屉菜单)
- ...
# 基础组件
# Text
文本组件,用于显示文本内容。RichText 和 TextSpan,富文本组件,用于显示富文本内容。
- TextDirection(文字方向)
- TextStyle(文字样式)
- colors(文字颜色)
- fontSize(文字大小)
- fontWeight(文字粗细)
- fontStyle(文字样式)
- TextAlign(文字对齐方式)
- TextOverflow(文字溢出方式)
- maxLines(最大行数)
# 设置自定义字体
- 下载并导入字体
- 字体网站:https://fonts.google.com/ (opens new window)
- 在项目根目录下创建 assets/fonts 目录,存放字体文件
- 在 pubspec.yaml 中配置字体
flutter:
fonts:
- family: LiuJianMaoCao
fonts:
- asset: assets/fonts/LiuJianMaoCao-Regular.ttf
- asset: assets/fonts/LiuJianMaoCao-Medium.ttf
weight: 500
- asset: assets/fonts/LiuJianMaoCao-SemiBold.ttf
weight: 600
- asset: assets/fonts/LiuJianMaoCao-Bold.ttf
weight: 700
- 使用字体
- 为整个应用设置默认字体
@override
Widget build(BuildContext context) {
return MaterialApp(
title: appName, home: Home(), theme: ThemeData(fontFamily: 'LiuJianMaoCao'));
}
- 为某个组件设置字体
Text('Hello World', style: TextStyle(fontFamily: 'LiuJianMaoCao'));
# Icon
TODO:如何自定义?
# Color
- ARGB,十六进制表示:透明度 + 红 + 绿 + 蓝
Color.fromARGB(255, 255, 255, 255)
- RGBO, 0-255 表示:红 + 绿 + 蓝 + 透明度
Color.fromRGBO(255, 255, 255, 1)
# Container
盒布局
- child:子组件
- padding(margin)
- EdgeInsets(all、fromLTRB、only)
- decoration
- BoxDecoration
- color
- borderRadius
- border
- boxShadow
- gradient
- image
- BoxDecoration
- alignment
- transform
- Matrix4
- translate
- rotate
- scale
- skew
- Matrix4
# Column 和 Row
线性布局(跟 flex 很像) Column 、 Row:
- mainAxisAlignment:主轴对齐方式
- crossAxisAlignment:交叉轴对齐方式
- children:子组件
# Flex
弹性布局,相对 Column 和 Row 可以指定主轴放心 Flex
- direction (声明主轴方向)
- mainAxisAlignment (声明主轴对产方式)
- textDirection (声明水平方向的排列顺序)
- crossAxisAlignment (声明交叉轴对齐方式)
- verticalDirection (声明垂直方向的排列顺序)
- children (声明子组件)
Expanded (可伸缩组件,自动充满剩余空间)
- flex (声明弹性布局所占比例)
- child (声明子组件)
Spacer (占位组件,自动充满剩余空间)
- flex (声明弹性布局所占比例)
# Wrap
流式布局,自动换行,解决内容溢出问题
- spacing (主轴方向间距)
- runSpacing (交叉轴方向间距)
- alignment (交叉轴对齐方式)
- runAlignment (主轴对齐方式)
# Stack
层叠布局,子组件可以重叠(类似 css z-index)
- alignment (子组件对齐方式)
- textDirection (子组件水平方向排列顺序)
# Positioned
层叠布局子组件,用于定位
- child (子组件)
- left、top、right、bottom (距离父组件边距)
- width、height (子组件宽高)
# Image
图片
- Image.asset('图片路径')
- 在 pubspec.yaml 中配置图片路径
flutter:
assets:
- assets/images/
# NetwordImage
网络图片组件
- Networklmage('图片地址')
- 配置 Android 权限 (AndroidManifest.xml)
<uses-permission android:name="android.permission.INTERNET"/>
- 配置允许访问 http
<application
android:usesCleartextTraffic="true"
...
>
# Button
- TextButton,text
- ElevatedButton,fill
- OutlinedButton,stroke
属性:
- onPressed (点击事件) onLongPress (长按事件)
- child (子组件)
- style (按钮样式 (opens new window))
# SingleChildScrollView
滚动组件 (opens new window),用于解决内容溢出问题
属性:
- child
- scrollDirection (滚动方向)
- reverse (是否反向滚动)
- physics (滚动到底部或顶部的行为)
- BouncingScrollPhysics (允许超出边界,回弹)
- ClampingScrollPhysics (不允许超出边界)
- NeverScrollableScrollPhysics (不允许滚动)
- AlwaysScrollableScrollPhysics (允许滚动)
# ListView
属性:
- children (子组件)
- padding (内边距)
- reverse (是否反向)
- scrollDirection (滚动方向)
第二种方式:
- itemCount (子组件数量)
- itemBuider (子组件构造器)
- separatorBuilder (分割线构造器)
# GridView
属性:
- children (子组件)
- scrollDirection (滚动方向)
- gridDelegate (网格代理)
- SliverGridDelegateWithFixedCrossAxisCount (固定列数 - 子组件宽度自适应)
- SliverGridDelegateWithMaxCrossAxisExtent (固定宽度 - 列数自适应)*
- GridView.count (固定列数,构造器)
- GridView.extent (固定宽度,构造器)*
- GridView.builder (子组件构造器)
- physics
# Cupertino
- Material 安卓风格:
import 'package:flutter/material.dart'; - Cupertino iOS 风格:
import 'package:flutter/cupertino.dart'; - 判断环境:
dart:io中的Platform.isIOS和Platform.isAndroid
# Safearea
安全区域组件,用于解决刘海屏、底部虚拟按键等问题
# DataTable
表格组件
# 导航组件
# BottomNavigationBar
底部导航栏组件
# DefaultTabController
TabBar + TabBarView (可滑动)
# 表单组件
# Switch
开关
# Checkbox
Checkbox 和 CheckboxListTile
# Radio
单选
# TextField
文本框
- autofocus(获取焦点)
- keyboardType(键盘类型)
- obscureText(是否隐藏输入内容)
- decoration(输入框样式)
- onChanged(输入框内容变化时的回调)
- labelText(输入框标题)
- hintText(输入框提示)
- maxLines(最大行数)
# Calendar
日历
- CalendarDatePicker(日历选择器)
- showCalendarMode(显示模式)
# Form
使用步骤:
- 创建
GlobalKey<FormState>key 对象 - 创建 Form 组件,传入 key
- 创建 TextFormField 组件,传入 validator(验证器)
- 验证表单:
key.currentState.validate()
class LoginForm extends StatefulWidget {
const LoginForm({super.key});
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>(); // 1. 创建一个全局的key
String? _phone;
@override
Widget build(BuildContext context) {
return Container(
child: Form(
key: _formKey, // 2. 将 key 绑定到 Form
child: Column(
children: [
TextFormField(
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
hintText: '手机号',
),
validator: (value) {
// 3. 添加验证规则
RegExp reg = RegExp(r'^\d{11}$');
if (!reg.hasMatch(value.toString())) {
return '请输入正确的手机号';
}
return null;
},
onSaved: (value) {
_phone = value.toString();// 6. 触发 save,保存数据
},
),
Row(
children: [
Expanded(
child: ElevatedButton(
child: const Text('提交'),
onPressed: () {
if (_formKey.currentState!.validate()) {
// 4. 调用 validate() 方法
print("提交成功");
// 5. 调用 save() 方法
_formKey.currentState!.save();
}
},
))
],
)
],
)));
}
}
# 第三方组件
# dio
dio (opens new window),类似 axios,用于请求数据
# flutter_swiper
flutter_swiper (opens new window),轮播组件,适配多种平台
# shared_preferences
shared_preferences (opens new window),本地数据缓存库(类似 AsyncStorage)
# 状态管理
Flutter 中的组件,按状态划分:
- statelessWidget (无状态组件)
- StatefulWidget (状态组件)
按状态作用域划分
- 组件内私有状态 (StatefulWidget)
- 跨组件状态共享 (Inheritedwidget、 Provider) 全局状态 (Redux fish-redux、Mobx .....)
状态组件的组成
- StatefulWidget (组件本身不可变- @immutable)
- state (将变化的状态放到 state 中维护)
# StatefulWidget (这里讲得很烂)
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'counter', home: Home());
}
}
class Home extends StatefulWidget {
const Home({super.key});
@override
State<Home> createState() => _HomeState();// 必须实现这个方法
}
class _HomeState extends State<Home> {// 泛型可以是 StatefulWidget 或其子类
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _decrementCounter() {
setState(() {
_counter--;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_counter.toString()),
ElevatedButton(onPressed: _incrementCounter, child: Icon(Icons.add)),
ElevatedButton(onPressed: _decrementCounter, child: Icon(Icons.remove)),
],
);
}
}
# InheritedWidget
提供了沿树向下,共享数据的能力。解决了跨组件状态共享的问题。
使用构造函数传参,若层级过深,代码会很冗余。

使用 InheritedWidget 传参,可以解决这个问题。

使用过程:
- 创建一个 InheritedWidget 组件,它相当于一个数据容器,用于存放需要共享的数据。
- 初始化这个数据容器,它应该包裹在需要共享数据的组件的外层,并同时传入初始数据。(体现沿树向下的特点)
- 在子组件中使用
InheritedWidget.of(context)获取这个数据容器的实例,从而获取到数据。
import 'package:flutter/material.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: 'InheritedWidge Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue),
),
home: const Home(),
);
}
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
int _count = 0;
void _updateNum(int num) {
setState(() {
_count += num;
});
}
@override
Widget build(BuildContext context) {
return ShareDataWidget(// 2. 将需要共享的数据放入数据容器
num: _count, //
updateNum: _updateNum,
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterTv(),
CounterControl(),
]));
}
}
class CounterControl extends StatelessWidget {
const CounterControl({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {// 3. 访问数据容器中的更新方法
ShareDataWidget.of(context)!.updateNum(1);
},
child: const Icon(Icons.add)),
ElevatedButton(
onPressed: () {
ShareDataWidget.of(context)!.updateNum(-1);
},
child: const Icon(Icons.remove)),
],
);
}
}
class CounterTv extends StatelessWidget {
const CounterTv({super.key});
@override
Widget build(BuildContext context) {
return Text(// 3. 访问数据容器中的数据
ShareDataWidget.of(context)!.num.toString(),
style: const TextStyle(fontSize: 30, color: Colors.yellow),
);
}
}
// 1. 声明数据容器,用于存放需要共享的数据
class ShareDataWidget extends InheritedWidget {
const ShareDataWidget(
{super.key,
required this.child,
required this.num,
required this.updateNum})
: super(child: child);
final int num; // 需要在子树中共享的数据 num
final Function updateNum;// 更新数据的方法
final Widget child;
static ShareDataWidget? of(BuildContext context) {
// 返回数据容器实例
return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
}
@override
bool updateShouldNotify(ShareDataWidget oldWidget) {
// 该回调决定当data发生变化时,是否通知子树中依赖data的Widget
return true;
}
}
# 生命周期
- 调用构造函数 StatefulWidget
- createElement 创建组件元素,并挂载到元素树上
- createState 创建组件状态 State
- initState 初始化状态
- didChangeDependencies 数据被更新,状态依赖改变
- 组件被标识为 dirty,为需要构建/更新的组件
- build 构建组件
- 组件被标识为 clean,为组件构建完成
或重新构建
- setState 状态被更新
或更新
- didUpdateWidget 组件被更新
或销毁:
- deactivate 组件被移除
- dispose 组件被销毁
didChangeDependencies 的调用时机:
- initState 后调用
- InheritedWidge 更新时调用
# Provider
Provider (opens new window),是第三方对 InheritedWidget 的封装。
优点:
- 简化资源的分配与处置
- 懒加载
原理:
Provider 遵循观察者模式,类似于 vue 的 MVVM:
- 被观察者:数据模型,Model
- 观察者:ViewModel
- 生产者、消费者:View,展示 或 触发数据初始化、更新
- ChangeNotifier 为数据仓库
- ChangeNotifierProvider 为 ViewModel
使用:
- 安装引入 Provider
- 创建数据模型(T extends ChangeNotifier)
- 创建 Provider(注册数据模型)
Provider(),不会被要求随着数据的变化而重新构建ChangeNotifierProvider(),会被要求随着数据的变化而被通知更新
- 获取数据模型并更新 UI
- 通过上下文
BuildContext - 通过静态方法
Provider.of<T>(context)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(title: 'Provider demo', home: Home());
}
}
class Home extends StatelessWidget {
const Home({super.key});
@override
Widget build(BuildContext context) {
// 2. 注册数据模型
return ChangeNotifierProvider(
create: (context) => LikesModel(),
child: Scaffold(
appBar: AppBar(title: const Text('Provider demo')),
body: const LikesWidget(),
),
);
}
}
// 3. 使用数据模型
class LikesWidget extends StatelessWidget {
const LikesWidget({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Likes: ${context.watch<LikesModel>().like}'), // 访问方式使用 context
TextButton(
onPressed: () {
Provider.of<LikesModel>(context, listen: false)
.addLike(); // 访问方式2:使用 Provider.of
},
child: Icon(Icons.thumb_up))
],
);
}
}
// 1.创建数据模型
class LikesModel extends ChangeNotifier {
int _like = 0;
int get like => _like;
void addLike() {
// 累加
_like++;
// 通知 UI 更新
notifyListeners();
}
}
# 路由与导航
- Route
- 一个路由是一个屏幕或页面的抽象
- Navigator
- 管理路由的组件。Navigator 可以通过路由入栈和出栈来实现页面之间的跳转
- 常用属性
- initialRoute: 初始路由,即默认页面
- onGenerateRoute: 动态路由 (根据规则,匹配动态路由)
- onUnknownRoute:未知路由,也就是 404
- routes:路由集合
# 匿名路由
- push
Navigator.push(context, MaterialPageRoute(builder: (context) => 组件名称()));
- pop
Navigator.pop(context);
具体用法:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(title: 'Provider demo', home: PageList());
}
}
class PageList extends StatelessWidget {
const PageList({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page List')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PageDetail()));
},
child: const Text("Route To Detail")),
));
}
}
class PageDetail extends StatelessWidget {
const PageDetail({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page Detail'),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back)),
),
body: const Center(
child: Text("Page Detial"),
),
);
}
}
# 命名路由
- 在
MaterialApp中配置路由集合routes - 使用
initialRoute指定默认路由 onUnknownRoute指定未知路由- 使用
Navigator.pushNamed(context, '路由名称')跳转到指定路由
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '命名路由 demo',
routes: {
'page_list': (context) => PageList(),
'page_detail': (context) => PageDetail()
},
initialRoute: 'page_list', // 指定首页
onUnknownRoute: (RouteSettings routeSettings) {
// 未知路由处理
return MaterialPageRoute(builder: (context) => Page404());
},
);
}
}
class PageList extends StatelessWidget {
const PageList({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page List')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, 'page_detail');
},
child: const Text("Route To Detail")),
ElevatedButton(
onPressed: () {// 测试跳转到未知路由
Navigator.pushNamed(context, 'hhhhhhhh');
},
child: const Text("hhhhhhhh"))
],
),
));
}
}
class PageDetail extends StatelessWidget {
const PageDetail({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page Detail'),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back)),
),
body: const Center(
child: Text("Page Detial"),
),
);
}
}
class Page404 extends StatelessWidget {
const Page404({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page 404'),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back)),
),
body: const Center(
child: Text("404"),
),
);
}
}
# 动态路由
通过 onGenerateRoute 指定的路由
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '命名路由 demo',
// 重点看这
onGenerateRoute: (RouteSettings settings) {
print("当前路径:$settings.name");
if (['/', '/page_list'].contains(settings.name)) {
return MaterialPageRoute(builder: (context) => const PageList());
}
// 匹配 /page_detail/:id
var uri = Uri.parse(settings.name.toString());
if (uri.pathSegments.length == 2 &&
uri.pathSegments.first == 'page_detail') {
var id = uri.pathSegments[1];
return MaterialPageRoute(builder: (context) => PageDetail(id: id));
}
return MaterialPageRoute(builder: (context) => Page404());
});
}
}
class PageList extends StatelessWidget {
const PageList({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page List')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/page_detail/1');
},
child: const Text("Route To Detail 1")),
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/page_detail/2');
},
child: const Text("Route To Detail 2")),
ElevatedButton(
onPressed: () {
// 测试跳转到未知路由
Navigator.pushNamed(context, 'hhhhhhhh');
},
child: const Text("hhhhhhhh"))
],
),
));
}
}
class PageDetail extends StatelessWidget {
const PageDetail({super.key, required this.id});
final String id;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page Detail'),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back)),
),
body: Center(
child: Text("Page Detial$id"),
),
);
}
}
class Page404 extends StatelessWidget {
const Page404({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page 404'),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back)),
),
body: const Center(
child: Text("404"),
),
);
}
}
# 路由传参
- 匿名路传参:主要是通过构造函数传参
- 命名路由传参:
- 跳转时传参
Navigator.pushNamed(context, routename, arguments: {参数A: 'A'}) - 页面中接收参数
ModalRoute.of(context)!.settings.arguments
# 动画(可以搜下王叔不秃 up)
补间动画(Tween)
- 定义开始点、结束点、时间线、转换时间、速度曲线。
- 例如透明度从 0 到 1,时间线为 0-1,转换时间为 1s,速度曲线为匀速。
拟物动画
- 对真实世界的模拟,例如弹簧、阻尼、重力、抛物线动画等。
# Animation
动画库的核心类 Animation,包含动画的值和状态。有两种 监听函数:
- 监听值的变化:
addListener、removeListener - 监听状态的变化:
addStatusListener、removeStatusListener
动画的状态AnimationStatus:
- dismissed:动画在起始点停止
- forward:动画正在正向执行
- reverse:动画正在反向执行
- completed:动画在终点停止
动画的控制器AnimationController:
- 在指定时间内,将组件初始值演变到终止值,从而形成动画。
- 属性:
- duration:动画时长
- reverseDuration:反向动画的时长,默认是 duration 的值
- value:动画的初始值,默认是 lowerBound
- lowerBound:动画的起始值,默认是 0
- upperBound:动画的终止值,默认是 1
- vsync:垂直同步信号,用于防止屏幕外动画消耗不必要的资源
- 方法:
- forward():正向执行动画
- reverse():反向执行动画
- dispose():释放资源(在不需要动画时调用,避免资源泄露)
- stop():停止动画
# Tween
- AnimationController 动画生成值的范围是 0-1,而 Tween 可以将其转换为任意值。
- Tween 的唯一职责就是定义从输入范围到输出范围的映射。例如颜色区间 0 ~ 255。
用法:
Tween<double>(begin:起始值, end:终止值);ColorTween(begin:Colors.white, end:Colors.black);
# CurvedAnimation
CurvedAnimation 用于定义动画的速度曲线。
CurvedAnimation(parent: controller, curve: Curves.easeIn, reverseCurve: Curves.easeIn);
- parent:动画控制器对象
- curve:正向动画的速度曲线
- reverseCurve:反向动画的速度曲线
# 动画实战
- 创建动画控制器:
controller = AnimationController(duration, vsync, value)
动画是通过 TickerProvider 来驱动的,所以需要传入一个 TickerProvider 对象,一般使用 State 对象作为 TickerProvider。 TickerProvider 通知组件每一帧的时间,从而驱动动画。
声明动画曲线,声明动画取值范围
监听动画的值和状态,调用 setState() 更新 UI
绑定动画值到组件
执行动画
controller.forward()、controller.reverse()释放动画
controller.dispose()
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: '动画', home: PageAnimation());
}
}
class PageAnimation extends StatelessWidget {
const PageAnimation({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('动画')),
body: const AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({super.key});
@override
State<AnimationDemo> createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation animation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建 AnimationController
controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this, // 因为混入了 SingleTickerProviderStateMixin 才能使用 this
);
// 2-1. 声明动画曲线
animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);
// 2-2. 声明动画值的范围
animation = Tween(begin: 50.0, end: 400.0)
.animate(controller); // 指定 animation.value 的范围
// 3. 监听动画
animation.addListener(() {
// print(animation.value);
setState(() {});
});
}
@override
void dispose() {
super.dispose();
controller.dispose(); // 6. 释放动画
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
ElevatedButton(
onPressed: () {
controller.forward(); // 5. 执行动画
},
child: Text('放大')),
ElevatedButton(
onPressed: () {
controller.reverse();
},
child: Text('缩小')),
ElevatedButton(
onPressed: () {
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
controller.forward();
},
child: Text('重复')),
ElevatedButton(
onPressed: () {
controller.stop();
},
child: Text('暂停')),
Icon(
Icons.favorite,
color: Colors.red,
size: animation.value, // 4.绑定动画值到组件, 这个值时从 50 到 400 的
),
Opacity(
opacity: controller.value, // 这个值时从 0 到 1 的
child: const Text('透明度动画'))
],
),
);
}
}
# 交织动画
交织动画是指多个单一动画叠加或拼接而成的动画。 例如:组件变化可能涉及高度、宽度、颜色、透明度等,这些动画可以同时进行。
- 需要给每个动画设置时间间隔
Interval - 需要使用 Transform 对组件进行变换
- Transform.translate:平移
- Transform.rotate:旋转
- Transform.scale:缩放
以下为拼接了三个动画的交织动画:
- 0 ~ 0.5:尺寸动画
- 0.5 ~ 0.8:颜色动画
- 0.8 ~ 1:旋转动画
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: '动画', home: PageAnimation());
}
}
class PageAnimation extends StatelessWidget {
const PageAnimation({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('动画')),
body: const AnimationDemo(),
);
}
}
class AnimationDemo extends StatefulWidget {
const AnimationDemo({super.key});
@override
State<AnimationDemo> createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
late Animation sizeAnimation;
late Animation colorAnimation;
late Animation rotateAnimation;
@override
void initState() {
// TODO: implement initState
super.initState();
// 1. 创建 AnimationController
controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this, // 因为混入了 SingleTickerProviderStateMixin 才能使用 this
);
// 2. 创建动画
animation =
CurvedAnimation(parent: controller, curve: const Interval(0.0, 0.5))
..addListener(() {
setState(() {});
});
// 3. 让动画反复执行
animation.addListener(() {
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
});
});
// 4.设置其它动画
sizeAnimation =
Tween(begin: 50.0, end: 200.0).animate(animation); // 0 ~ 0.5
colorAnimation = ColorTween(begin: Colors.red, end: Colors.blue).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0.5, 0.8, curve: Curves.bounceIn)))
..addListener(() {
setState(() {});
});
rotateAnimation = Tween(begin: 0.0, end: 2 * pi).animate(CurvedAnimation(
parent: controller,
curve: const Interval(0.8, 1.0, curve: Curves.bounceIn)))
..addListener(() {
setState(() {});
});
}
@override
void dispose() {
super.dispose();
controller.dispose(); // 6. 释放动画
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
ElevatedButton(
onPressed: () {
controller.forward();
},
child: const Text('重复')),
ElevatedButton(
onPressed: () {
controller.stop();
},
child: const Text('暂停')),
Opacity(// 5. 绑定动画值到 UI
opacity: controller.value, // 这个值时从 0 到 1 的
child: Transform.rotate(
angle: rotateAnimation.value,
child: Container(
width: sizeAnimation.value,
height: sizeAnimation.value,
color: colorAnimation.value,
)))
],
),
);
}
}
# Hero 动画
Hero 动画用来实现跨页面动画效果。
- 在不同页面中,声明一个共享组件
Hero - 由于共享组件在不同页面中的位置、外观等不同,路由切换时,形成动画效果
步骤:
- 在页面 A 中定义起始 Hero 组件(source hero),声明tag(一个共享组件的标记)
- 在页面 B 中定义目标 Hero 组件(target hero),绑定相同的 tag
- 页面跳转时,通过 Navigator,传递 tag
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'Hero 动画', home: PageAnimation());
}
}
class PageAnimation extends StatelessWidget {
const PageAnimation({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hero 动画')),
body: const HeroAni(),
);
}
}
class HeroAni extends StatelessWidget {
const HeroAni({super.key});
@override
Widget build(BuildContext context) {
return Container(
child: GridView.extent(
maxCrossAxisExtent: 300,
mainAxisSpacing: 10,
children: List.generate(20, (index) {
String imageUrl = "https://picsum.photos/200/300?random=$index";
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return ImageDetail(imageUrl);
},
),
);
},
child: Hero(
tag: imageUrl, // 使用图片地址当作标记
child: Image.network(imageUrl),
),
);
}),
),
);
}
}
class ImageDetail extends StatelessWidget {
final String imageUrl;
const ImageDetail(this.imageUrl, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Hero(
tag: imageUrl,
child: Flex(
direction: Axis.vertical,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.network(
imageUrl,
width: double.infinity,
fit: BoxFit.cover,
)
]),
)));
}
}
# 多语言
# 组件多语言
- 安装国际化组件 flutter_localizations (opens new window)
- 在 MaterialApp 中配置国际化组件
import 'package:flutter_localizations/flutter_localizations.dart';
...
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '命名路由 demo',
home: PageLogin(),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate, // Material 组件库国际化
GlobalCupertinoLocalizations.delegate, // iOS 风格的组件国际化
GlobalWidgetsLocalizations.delegate // 文本方向和本地化的组件
],
supportedLocales: [
const Locale('zh', 'CN'), // 中文简体
const Locale('en', 'US'), // 美国英语
],
);
}
...
- 切换系统语言查看一下,一般日历那些会自动跟随系统语言
# 文本多语言
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter/foundation.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '命名路由 demo',
home: PageLogin(),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
CustomLocalizations.delegate // 配置自定义的代理
],
supportedLocales: [
const Locale('zh', 'CN'), // 中文简体
const Locale('en', 'US'), // 美国英语
],
);
}
}
class PageLogin extends StatelessWidget {
const PageLogin({super.key});
@override
Widget build(BuildContext context) {
print(Localizations.of(context, CustomLocalizations));
return Scaffold(
appBar: AppBar(title: const Text('Form')),
body: Center(
child: Text(
// "Hello world",
Localizations.of(context, CustomLocalizations).translate('title'),
style: TextStyle(fontSize: 50))),
);
}
}
// 本地化代理类
class CustomLocalizationsDelegate
extends LocalizationsDelegate<CustomLocalizations> {
@override
bool isSupported(Locale locale) {
return ['en', 'zh'].contains(locale.languageCode);
}
@override
Future<CustomLocalizations> load(Locale locale) {
return SynchronousFuture(
CustomLocalizations(locale)); // 依赖 package:flutter/foundation.dart
}
@override
bool shouldReload(LocalizationsDelegate<CustomLocalizations> old) {
return false;
}
}
class CustomLocalizations {
final Locale locale;
CustomLocalizations(this.locale);
// // 声明字典
static final Map<String, Map<String, String>> _localizedValues = {
'en': {
'title': 'Hello 你',
},
'zh': {
'title': '你好',
},
};
// 根据 key 获取 value
String translate(String key) {
return _localizedValues[locale.languageCode]![key]!;
}
static CustomLocalizationsDelegate delegate = CustomLocalizationsDelegate();
}
# 加载语言包
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter/foundation.dart';
import 'dart:convert';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '命名路由 demo',
home: PageLogin(),
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
CustomLocalizations.delegate // 配置自定义的代理
],
supportedLocales: [
const Locale('zh', 'CN'), // 中文简体
const Locale('en', 'US'), // 美国英语
],
localeResolutionCallback: (locale, supportedLocales) {
print("deviceLocale: $locale");
print("languageCode: ${locale!.languageCode}");
print("countryCode: ${locale.countryCode}");
for (var supportedLocale in supportedLocales) {
if (supportedLocale.languageCode == locale.languageCode &&
supportedLocale.countryCode == locale.countryCode) {
return supportedLocale;
}
}
return supportedLocales.first;
},
);
}
}
class PageLogin extends StatelessWidget {
const PageLogin({super.key});
@override
Widget build(BuildContext context) {
print(Localizations.of(context, CustomLocalizations));
return Scaffold(
appBar: AppBar(title: const Text('Form')),
body: Center(
child: Text(
// "Hello world",
Localizations.of(context, CustomLocalizations).translate('title'),
style: TextStyle(fontSize: 50))),
);
}
}
// 本地化代理类
class CustomLocalizationsDelegate
extends LocalizationsDelegate<CustomLocalizations> {
@override
bool isSupported(Locale locale) {
return ['en', 'zh'].contains(locale.languageCode);
}
@override
Future<CustomLocalizations> load(Locale locale) async {
CustomLocalizations localizations = CustomLocalizations(locale);
await localizations.loadJSON();
return SynchronousFuture(localizations);
}
@override
bool shouldReload(LocalizationsDelegate<CustomLocalizations> old) {
return false;
}
}
class CustomLocalizations {
final Locale locale;
CustomLocalizations(this.locale);
late Map<String, String> _localizedValues;
Future<bool> loadJSON() async {
String jsonString = await rootBundle.loadString(
'lang/${locale.languageCode}.json'); // import 'package:flutter/services.dart';
Map<String, dynamic> jsonMap =
json.decode(jsonString); // import 'dart:convert';
_localizedValues = jsonMap.map((key, value) {
return MapEntry(key, value.toString());
});
return true;
}
// 根据 key 获取 value
String translate(String key) {
return _localizedValues[key]!;
}
static CustomLocalizationsDelegate delegate = CustomLocalizationsDelegate();
}
创建 lang 目录,并新增 zh.json 和 en.json。 在 yaml 中配置语言包资源,然后通过 rootBundle 加载
assets:
- lang/en.json
- lang/zh.json
# 多主题
最简单的适配:
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
更自定义的方法:
CustomTheme.dart
import 'package:flutter/material.dart';
class CustomTheme{
// 公共样式
Static const double _titleFontSize = 20;
// 亮色主题
static final ThemeData lightTheme = ThemeData(
primarySwatch: Colors.blue,
primaryColor: Colors.white,
backgroundColor: Colors.grey[100],
accentColor: Colors.blue,
accentIconTheme: IconThemeData(color: Colors.white),
dividerColor: Colors.white54,
fontFamily: 'Montserrat',
textTheme: TextTheme(
headline1: TextStyle(fontSize: _titleFontSize, color: Colors.black),
headline2: TextStyle(fontSize: _titleFontSize, color: Colors.white),
bodyText1: TextStyle(fontSize: 14.0, color: Colors.black87),
bodyText2: TextStyle(fontSize: 14.0, color: Colors.white),
),
);
// 暗色主题
static final ThemeData darkTheme = ThemeData(
primarySwatch: Colors.grey,
primaryColor: Colors.black,
backgroundColor: Colors.grey[850],
accentColor: Colors.white,
accentIconTheme: IconThemeData(color: Colors.black),
dividerColor: Colors.black12,
fontFamily: 'Montserrat',
textTheme: TextTheme(
headline1: TextStyle(fontSize: _titleFontSize, color: Colors.white),
headline2: TextStyle(fontSize: _titleFontSize, color: Colors.black),
bodyText1: TextStyle(fontSize: 14.0, color: Colors.white70),
bodyText2: TextStyle(fontSize: 14.0, color: Colors.black),
),
);
}
# 获取主题上下文
Theme.of(context)
← Flutter 环境搭建 项目技术 →

