【开发经验】Flutter组件的事件传递与数据控制

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才具有statestate才具有传统意义上的生命周期(而不是页面),通过这些周期,可以做到一进入页面,就开始从服务器加载数据,也可以让一个Widget自动播放动画

我们先看这个需求:

描述:一个Widget自己改变自己的值
实现功能:倒计时,从网络加载数据

这也是一个常见的需求,但是很多新手写到这里就不会写了,可能会错误的去使用FutureBuilder进行网络请求,会造成每次都反复请求,实际上这里是必须使用StatefulWidgetstate来储存请求返回信息的。

一般项目中,动画,倒计时,异步请求此类功能需要使用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 _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中加载数据,每个页面都由一个StatefulScaffold和若干StatelessWidget组成,由ScaffoldState管理所有数据,再刷新即可。

注意,即使这个页面的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收到事件后,触发childstate的方法
实现功能:点击按钮让一个child开始倒计时或者发送请求(调用state的方法)
难度:⭐⭐⭐⭐⭐

最后

为了方便有学习需要的朋友,我把资料都整理成了视频教程(实际上比预期多花了不少精力)

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,没有人能随随便便成功。

加油,共勉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值