前言
对于有些场景,我们可能有多个组件共享一份状态数据,但是状态数据改变后可能只需要更新其中的一个或多个组件,而不是依赖状态的全部组件。这个时候我们就可以用到 GetX
的定向更新。GetX
在 update
方法中可以提供两个可选参数:
void update([List
-
ids
:要更新的id
数组,id
可以在GetBuilder
构建的时候指定,若指定了ids
,则之后更新与ids
中的id
匹配的组件:
GetBuilder(
id: 'text'
init: Controller(), // use it only first time on each controller
builder: (_) => Text(
'${Get.find().counter}', //here
),
),
-
condition
:条件表达式,只有当这个条件为真的时候才会更新组件。
例如下面的代码只会在 counter
小于10
的时候更新 id
为 text
的组件。
update(['text'], counter < 10);
接下来我们模拟带倒计时的红绿灯来演示GetX
的定向更新的使用。
业务逻辑
为了分别控制红绿灯,我们需要三个组件,分别是红灯、绿灯和黄灯。
然后是倒计时,我们设置规则如下:
- 绿灯亮的时长为20秒,红灯为10秒,黄灯为3秒,计时通过定时器完成,每隔1秒减1。
- 三个红灯共用一个计时器,但根据当前亮的灯的状态来定向更新哪个灯的倒计时时间,同时对于不亮的灯我们不显示倒计时时间(因为共享了倒计时时间,如果显示就会不对)。
- 使用一个枚举来确定当前亮哪个灯,亮灯的次序为绿灯->黄灯->红灯->绿灯……
业务理顺了,开始撸代码!
红绿灯代码
首先我们构建一个通用的交通灯的组件 TrafficLed
,需要四个参数:
- 灯的颜色:
ledColor
,控制灯的倒计时数字颜色; - 倒计时时间:
secondsLeft
,倒计时时间; - 是否显示倒计时:
showSeconds
,使用 Offstate 控制是否显示倒计时时间。 - 灯的大小:
ledSize
,默认尺寸为60,用于控制灯的尺寸。
对应代码很简单,这里我们为了好看做了点阴影,使得看起来有点立体感。
class TrafficLed extends StatelessWidget {
final Color ledColor;
final int secondsLeft;
final bool showSeconds;
final double ledSize;
const TrafficLed({
Key? key,
required this.ledColor,
required this.secondsLeft,
required this.showSeconds,
this.ledSize = 60.0,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Container(
alignment: Alignment.center,
width: ledSize,
height: ledSize,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(ledSize / 2),
boxShadow: [
BoxShadow(
color: Color(0xFF505050),
offset: Offset(1, -1),
blurRadius: 0.2,
)
],
),
child: Offstage(
child: Text(
'$secondsLeft',
textAlign: TextAlign.center,
style: TextStyle(
color: this.ledColor,
fontSize: 36,
fontWeight: FontWeight.bold,
),
),
offstage: !showSeconds,
),
),
);
}
}
接下来是整个灯的组合,这里我们使用横向的红绿灯,然后也用阴影做了一个有立体感的背景。关键代码在每个灯都使用了 GetBuilder 包裹,然后指定了每个灯的 id,这里以绿灯为例:
GetBuilder(
id: 'green',
init: lightController,
builder: (state) => TrafficLed(
ledColor: (state.currentLight == TrafficLight.green
? Colors.green
: Colors.black),
secondsLeft: state.counter,
showSeconds: state.currentLight == TrafficLight.green,
),
),
每个灯对应的逻辑如下:
- 如果当前状态中显示的灯和自身一致,倒计时的文字颜色就使用灯对应的颜色,即红、黄、绿;否则显示黑色(和背景色一致);
- 绑定状态对象的倒计时时间;
- 如果当前状态中显示的灯和自身一致,则显示倒计时,否则不显示。
状态管理代码
状态管理控制器为TrafficLightController
,代码如下:
enum TrafficLight { green, red, yellow }
class TrafficLightController extends GetxController {
late TrafficLight _currentLight;
get currentLight => _currentLight;
int _counter = 0;
get counter => _counter;
late Timer _downcountTimer;
@override
void onInit() {
_counter = 20;
_currentLight = TrafficLight.green;
super.onInit();
}
@override
void onReady() {
_downcountTimer = Timer.periodic(Duration(seconds: 1), decreament);
super.onReady();
}
void decreament(Timer timer) {
_counter--;
if (_counter == 0) {
switch (_currentLight) {
case TrafficLight.green:
_currentLight = TrafficLight.yellow;
_counter = 3;
update(['green', 'yellow']);
break;
case TrafficLight.yellow:
_currentLight = TrafficLight.red;
_counter = 10;
update(['red', 'yellow']);
break;
case TrafficLight.red:
_currentLight = TrafficLight.green;
_counter = 20;
update(['red', 'green']);
break;
}
} else {
switch (_currentLight) {
case TrafficLight.green:
update(['green']);
break;
case TrafficLight.yellow:
update(['yellow']);
break;
case TrafficLight.red:
update(['red']);
break;
}
}
}
@override
void onClose() {
_downcountTimer.cancel();
super.onClose();
}
}
这里使用了三个声明周期函数:
-
onInit
:设置倒计时时间为20秒,灯状态为绿灯; -
onReady
:启动定时器; -
onClose
:关闭定时器。
核心业务逻辑都在 decreament
这个定时器回调方法里,这里如果倒计时到0的时候我们切换灯状态,重置倒计时时间,而且只更新该情况下需要刷新的灯(每次2个灯需要更新)。如果倒计时没有到0,那么我们只需要更新当前亮的灯就可以了。通过这种方式,我们可以定向更新,在共享状态数据的同时还可以减少不必要的刷新。
运行效果
运行效果如下图,源码已提交至:GetX 相关代码。
总结
本篇介绍了 GetX
的 GetBuilder
使用id
参数实现定向刷新的特性。这种情况适用于多个组件共用一个状态对象,但更新条件不同的情况,比如本例的红绿灯。同时,GetxController
的 update
方法还可以实现条件更新,通过id
加条件组合能够实现更加进货精准的定向刷新。