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

本文详细介绍了如何在Flutter中使用StatefulWidget管理Widget的状态,包括使用State类处理数据变化、动画控制(如计数器递减和位置动画),以及推荐避免ListView管理自身state,提倡使用Scaffold和Stateful组件进行数据管理。还讨论了单例模式在全局数据和事件管理中的应用。
摘要由CSDN通过智能技术生成

}

可以试着让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的方法)
难度:⭐⭐⭐⭐⭐

首先必须明确的是,如果出现在业务逻辑里,这里是显然不合理,是需要避免的。StatefulWidget嵌套时应当避免互相调用方法,在这种时候,最好是将childstate中的方法与数据,向上提取放到当前层state中。

这里可以简单分析一下:

  1. 有数据变化
    有数据变化时,使用StatedidUpdateWidget生命周期更加合理。这里我们也可以勉强实现一下,在flutter框架中,我推荐使用ValueNotifier进行传递,child监听ValueNotifier即可。
  2. 没有数据变化
    没有数据变化就比较麻烦了,我们需要一个controller进去,然后child注册一个回调进controller,这样就可以通过controller控制。

这里也可以使用providereventbus等库,或者用keyglobalKey相关方法实现。但是,必须再强调一次:不管用什么方式实现,这种嵌套是不合理的,项目中需要互相调用state的方法时,应当合并写在一个state里。原则上,需要避免此种嵌套,无论如何实现,都不应当是项目中的通用做法。

虽然不推荐在业务代码中这样写,但是在框架的代码中是可以写这种结构的(因为必须暴露接口)。这种情况可以参考ScrollController,你可以通过这个Controller控制滑动状态。

值得一提的是:ScrollController继承自ValueNotifier。所以使用ValueNotifier仍然是推荐做法。

其实controller模式也是flutter源码中常见的模式,一般用于对外暴露封装的方法。controller相比于其他的方法,比较复杂,好在我们不会经常用到。

作为例子,让我们实现一个CountController类,来帮我们调用组件内部的方法。

代码:

class CountController extends ValueNotifier {
CountController(int value) : super(value);

// 逐个增加到目标数字
Future 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;
}
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

分享读者

作者2013年java转到Android开发,在小厂待过,也去过华为,OPPO等大厂待过,18年四月份进了阿里一直到现在。

被人面试过,也面试过很多人。深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长,而且极易碰到天花板技术停滞不前!

我们整理了一份阿里P7级别的Android架构师全套学习资料,特别适合有3-5年以上经验的小伙伴深入学习提升。

主要包括腾讯,以及字节跳动,阿里,华为,小米,等一线互联网公司主流架构技术。

腾讯T3架构师学习专题资料

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

,等一线互联网公司主流架构技术。

[外链图片转存中…(img-dxr9Tgcs-1713439259147)]

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 23
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值