本文应用原生
Flutter
模式设计代码,只讲最根底的货色,不应用任何其余第三方库(Provider
等)
写了靠近两年的 Flutter
,发现数据与事件的传递是老手在学习时常常问的问题:有很多初学者谬误的在十分晚期就引入provider
,BLOC
等模式去治理数据,适量应用内部框架 ,造成我的项目凌乱难以组织代码。其次要的起因就是因为 漠视了根底的,最简略的数据传递形式。
很难设想有人把全副数据放在一个顶层
provider
里,而后相对不写StatefulWidget
。这种我的项目反正我是不保护,谁爱看谁看。
本文会列举根本的事件与办法传递形式,并且举例子讲明如何应用根底的形式实现这些性能。本文的例子都基于 flutter
默认的加法 demo 批改,在 dartpad
或者新建 flutter
我的项目中即可运行本我的项目的代码例子。
在部分传递数据与事件
先来看下根本的几个利用状况,只有实现了这些状况,在部分就能够十分晦涩的传递数据与事件:
留神思考:下文的
Widget
,哪些是StatefulWidget
?
形容:一个 Widget
收到事件后,扭转 child 显示的值
实现性能:点击加号让数字 +1
难度:⭐
形容:一个 Widget
在 child 收到事件时,扭转本人的值
实现性能:点击扭转页面色彩
难度:⭐
形容:一个 Widget
在 child 收到事件时,触发本人的 state 的办法
实现性能:点击发动网络申请,刷新以后页面
难度:⭐
形容:一个 Widget
本人扭转本人的值
实现性能:倒计时,从网络加载数据
难度:⭐⭐⭐
形容:一个 Widget
本人的数据变动时,触发 state
的办法
实现性能:一个在数据扭转时播放过渡动画的组件
难度:⭐⭐⭐⭐
形容:一个 Widget
收到事件后,触发 child
的state
的办法
实现性能:点击按钮让一个 child
开始倒计时或者发送申请
难度:⭐⭐⭐⭐⭐
咱们平时写我的项目根本也就是下面这些需要了,只有学会实现这些事件与数据传递,就能够轻松写出任何我的项目了。
应用回调传递事件
应用简略的回调就能够实现这几个需要,这也是整个 flutter
的根底:如何扭转一个 state
内的数据,以及如何扭转一个 widget
的数据。
形容:一个
widget
收到事件后,扭转child
显示的值
实现性能:点击加号让数字 +1形容:一个
widget
在child
收到事件时,扭转本人的值
实现性能:点击扭转页面色彩形容:一个
widget
在child
收到事件时,触发本人的state
的办法
实现性能:点击发动网络申请,刷新以后页面
这几个都是毫无难度的,咱们间接看同一段代码就行了
代码:
/// 这段代码是应用官网的代码批改的,通常状况下,只须要应用回调就能获取点击事件
class MyHomePage extends StatefulWidget {MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
// 在按钮的回调中,你能够设置数据与调用办法
// 在这里,让计数器 + 1 后刷新页面
setState(() {_counter++;});
}
// setState 后就会应用新的数据从新进行 build
// flutter 的 build 性能十分强,甚至反对每秒 60 次 rebuild
// 所以不用过于放心触发 build,然而要偶然留神超大范畴的 build
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title),
),
body: Center(
child: Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
),
floatingActionButton: _AddButton(onAdd: _incrementCounter,),
);
}
}
/// 个别会应用 GestureDetector 来获取点击事件
/// 因为官网的 FloatingActionButton 会自带款式,个别咱们会本人写按钮款式
class _AddButton extends StatelessWidget {
final Function onAdd;
const _AddButton({Key key, this.onAdd}) : super(key: key);
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: onAdd,
child: Icon(Icons.add),
);
}
}
这种形式非常的简略,只须要在回调中扭转数据,再 setState
就会触发 build
办法,依据以后的数据从新 build
以后 widget
,这也是flutter
最根本的刷新办法。
在 State 中扭转数据
在 flutter
中,只有 StatefulWidget
才具备 state
,state
才具备传统意义上的生命周期(而不是页面),通过这些周期,能够做到一进入页面,就开始从服务器加载数据,也能够让一个 Widget
自动播放动画
咱们先看这个需要:
形容:一个
Widget
本人扭转本人的值
实现性能:倒计时,从网络加载数据
这也是一个常见的需要,然而很多新手写到这里就不会写了,可能会谬误的去应用 FutureBuilder
进行网络申请,会造成每次都重复申请,实际上这里是必须应用 StatefulWidget
的state
来贮存申请返回信息的。
个别我的项目中,动画,倒计时,异步申请此类性能须要应用state
,其余大多数的性能并不需要存在state
。
例如这个widget
, 会显示一个数字:
class _CounterText extends StatelessWidget {
final int count;
const _CounterText({Key key, this.count}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(child: Text('$count'),
);
}
}
能够试着让 widget 从服务器加载这个数字:
class _CounterText extends StatefulWidget {const _CounterText({Key key}) : super(key: key);
@override
__CounterTextState createState() => __CounterTextState();
}
class __CounterTextState extends State<_CounterText> {
@override
void initState() {
// 在 initState 中发出请求
_fetchData();
super.initState();}
// 在数据加载之前,显示 0
int count = 0;
// 加载数据, 模仿一个异步, 申请后刷新
Future<void> _fetchData() async {await Future.delayed(Duration(seconds: 1));
setState(() {count = 10;});
}
@override
Widget build(BuildContext context) {
return Center(child: Text('$count'),
);
}
}
又或者,咱们想让这个数字每秒都减 1,最小到 0。那么只须要把他变成 stateful 后,在 initState 中初始化一个 timer,让数字减小:
class _CounterText extends StatefulWidget {
final int initCount;
const _CounterText({Key key, this.initCount:10}) : super(key: key);
@override
__CounterTextState createState() => __CounterTextState();
}
class __CounterTextState extends State<_CounterText> {
Timer _timer;
int count = 0;
@override
void initState() {
count = widget.initCount;
_timer = Timer.periodic(Duration(seconds: 1),
(timer) {if (count > 0) {setState(() {count--;});
}
},
);
super.initState();}
@override
void dispose() {_timer?.cancel();
super.dispose();}
@override
Widget build(BuildContext context) {
return Center(child: Text('${widget.initCount}'),
);
}
}
这样咱们就能看到这个 widget
从输出的数字每秒缩小 1。
由此可见,widget
能够在 state
中扭转数据,这样咱们在应用 StatefulWidget
时,只须要给其初始数据,widget
会依据生命周期加载或扭转数据。
在这里,我倡议的用法是在 Scaffold
中加载数据,每个页面都由一个 Stateful
的Scaffold
和若干 StatelessWidget
组成,由 Scaffold
的State
治理所有数据,再刷新即可。
留神,即便这个页面的 body 是 ListView
,也不举荐ListView
治理本人的 state
,在以后state
保护数据的 list
即可。应用 ListView.builder
构建列表即可防止更新数组时,在页面上刷新列表的全副元素,放弃高性能刷新。
在 State 中监听 widget 变动
形容:一个
Widget
本人的数据变动时,触发state
的办法
实现性能:一个在数据扭转时播放过渡动画的组件
做这个之前,咱们先看一个简略的需要:一行widget
,承受一个数字,数字是偶数时,间隔右边24px
,奇数时间隔右边60px
。
这个必定很简略,咱们间接 StatelessWidget
就写进去了;
class _Row extends StatelessWidget {
final int number;
const _Row({
Key key,
this.number,
}) : super(key: key);
double get leftPadding => number % 2 == 1 ? 60.0 : 24.0;
@override
Widget build(BuildContext context) {
return Container(
height: 60,
width: double.infinity,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: leftPadding,),
child: Text('$number'),
);
}
}
这样就简略的实现了这个成果,然而理论运行的时候发现,数字左右横跳,很不美观。看来就有必要优化这个widget
,让他左右挪动的时候播放动画,挪动过来,而不是跳来跳去。
一个比较简单的计划是,传入一个 AnimationController
来准确管制,然而这样太简单了。这种场景下,咱们在应用的时候通常只想更新数字,再 setState,就心愿他在外部播放动画(通常是过渡动画),就能够不必去操作简单的 AnimationController
了。
实际上,这个时候咱们应用 didUpdateWidget
这个生命周期就能够了,在 state
所附丽的 widget
更新时,就会触发这个回调,你能够在这里响应下层传递的数据的更新,在外部播放动画。
代码:
class _Row extends StatefulWidget {
final int number;
const _Row({
Key key,
this.number,
}) : super(key: key);
@override
__RowState createState() => __RowState();
}
class __RowState extends State<_Row> with TickerProviderStateMixin {
AnimationController animationController;
double get leftPadding => widget.number % 2 == 1 ? 60.0 : 24.0;
@override
void initState() {
animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
lowerBound: 24,
upperBound: 60,
);
animationController.addListener(() {setState(() {});
});
super.initState();}
// widget 更新,就会触发这个办法
@override
void didUpdateWidget(_Row oldWidget) {
// 播放动画去以后地位
animationController.animateTo(leftPadding);
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {animationController.dispose();
super.dispose();}
@override
Widget build(BuildContext context) {
return Container(
height: 60,
width: double.infinity,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: animationController.value,),
child: Text('${widget.number}'),
);
}
}
这样在状态之间就实现了一个十分平滑的动画切换,再也不会左右横跳了。
办法 3: 传递 ValueNotifier/ 自定义 Controller
这里咱们还是先看需要
形容:一个
Widget
收到事件后,触发child
的state
的办法
实现性能:点击按钮让一个child
开始倒计时或者发送申请(调用 state 的办法)
难度:⭐⭐⭐⭐⭐
首先必须明确的是,如果呈现在业务逻辑里,这里是 显然不合理 ,是须要防止的。StatefulWidget
嵌套时 该当防止相互调用办法 ,在这种时候,最好是将child
的state
中的办法与数据,向上提取放到以后层 state
中。
这里能够简略剖析一下:
- 有数据变动
有数据变动时,应用State
的didUpdateWidget
生命周期更加正当。这里咱们也能够勉强实现一下,在flutter
框架中,我举荐应用ValueNotifier
进行传递,child
监听ValueNotifier
即可。 - 没有数据变动
没有数据变动就比拟麻烦了,咱们须要一个controller
进去,而后child
注册一个回调进controller
,这样就能够通过controller
管制。
这里也能够应用
provider
,eventbus
等库,或者用key
,globalKey
相干办法实现。然而,必须再强调一次:不论用什么形式实现,这种嵌套是不合理的,我的项目中须要相互调用 state 的办法时,该当合并写在一个state
里。原则上,须要防止此种嵌套,无论如何实现,都不该当是我的项目中的通用做法。
尽管不举荐在业务代码中这样写,然而在框架的代码中是能够写这种构造的(因为必须裸露接口)。这种状况能够参考 ScrollController
,你能够通过这个Controller
管制滑动状态。
值得一提的是:ScrollController
继承自 ValueNotifier
。所以应用ValueNotifier
依然是举荐做法。
其实 controller
模式也是 flutter
源码中常见的模式,个别用于对外裸露封装的办法。controller
相比于其余的办法,比较复杂,好在咱们不会常常用到。
作为例子,让咱们实现一个 CountController
类,来帮咱们调用组件外部的办法。
代码:
class CountController extends ValueNotifier<int> {CountController(int value) : super(value);
// 一一减少到指标数字
Future<void> countTo(int target) async {
int delta = target - value;
for (var i = 0; i < delta.abs(); i++) {await Future.delayed(Duration(milliseconds: 1000 ~/ delta.abs()));
this.value += delta ~/ delta.abs();}
}
// 切实想不出什么例子了,总之是能够这样调用办法
void customFunction() {_onCustomFunctionCall?.call();
}
// 指标 state 注册这个办法
Function _onCustomFunctionCall;
}
class _Row extends StatefulWidget {
final CountController controller;
const _Row({
Key key,
@required this.controller,
}) : super(key: key);
@override
__RowState createState() => __RowState();
}
class __RowState extends State<_Row> with TickerProviderStateMixin {
@override
void initState() {widget.controller.addListener(() {setState(() {});
});
widget.controller._onCustomFunctionCall = () {print('响应办法调用');
};
super.initState();}
// 这里 controller 应该是在里面 dispose
// @override
// void dispose() {// widget.controller.dispose();
// super.dispose();
// }
@override
Widget build(BuildContext context) {
return Container(
height: 60,
width: double.infinity,
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 24,),
child: Text('${widget.controller.value}'),
);
}
}
应用 controller
能够齐全管制下一层 state
的数据和办法调用,比拟灵便。然而代码量大,业务中该当防止写这种模式,只在简单的中央构建 controller
来控制数据。如果你写了很多自定义controller
,那应该反思你的我的项目构造是不是出了问题。无论如何实现,这种传递形式都不该当是我的项目中的通用做法。
单例治理全局数据与事件
全局的数据,能够应用顶层
provider
或者单例治理,我的习惯是应用单例,这样获取数据能够不依赖context
。
简略的单例写法,扩大任何属性到单例即可。
class Manager {
// 工厂模式
factory Manager() =>_getInstance();
static Manager get instance => _getInstance();
static Manager _instance;
Manager._internal() {// 初始化}
static Manager _getInstance() {if (_instance == null) {_instance = new Manager._internal();
}
return _instance;
}
}
总结
作者:马嘉伦
日期:2020/07/22
平台:Segmentfault,勿转载
我的其余文章:
【开发教训】Flutter 防止代码嵌套,写好 build 办法
【Flutter 工具】fmaker: 主动生成倍率切图 / 主动更换 App 图标
【Flutter 利用】Flutter 精仿抖音开源
【Flutter 工具】可能是 Flutter 上最简略的本地数据保留计划
写这篇文章的起因,是因为看到不少人在学习 flutter
时,对于数据与事件的传递十分的不相熟,又很早的去学习 provider
等第三方框架,对于根底的货色又只知其一; 不知其二,导致代码凌乱我的项目凌乱,不知如何传递数据,如何去刷新界面。所以写这篇文章总结了最根底的各种事件与数据的传递办法。
简略总结,flutter
扭转数据最根底的就是这么几种模式:
- 扭转本人
state
的数据,setState
向child
传递新数据 - 承受
child
的事件回调 - 向
child
更新指标数据,child
监听数据的变动,更加细节的扭转本人的state
- 向
child
传递controller
,全面管制child
的state
我的项目中只须要这几种模式就能很简略的全副写完了,应用 provider
等其余的库,代码上并不会有特地大的改善和提高。还是心愿大家学习 flutter
的时候,能先摸清根本的写法,再进行更深层次的学习。