Flutter 入门与实战(七十五):模拟红绿灯来看GetX的定向刷新

2,807 阅读3分钟

前言

对于有些场景,我们可能有多个组件共享一份状态数据,但是状态数据改变后可能只需要更新其中的一个或多个组件,而不是依赖状态的全部组件。这个时候我们就可以用到 GetX 的定向更新。GetXupdate 方法中可以提供两个可选参数:

void update([List<Object>? ids, bool condition = true]) {
  if (!condition) {
    return;
  }
  if (ids == null) {
    refresh();
  } else {
    for (final id in ids) {
      refreshGroup(id);
    }
  }
}
  • ids:要更新的 id数组,id可以在 GetBuilder 构建的时候指定,若指定了ids,则之后更新与 ids 中的 id 匹配的组件:
GetBuilder<Controller>(
  id: 'text'
  init: Controller(), // use it only first time on each controller
  builder: (_) => Text(
    '${Get.find<Controller>().counter}', //here
  ),
),
  • condition:条件表达式,只有当这个条件为真的时候才会更新组件。

例如下面的代码只会在 counter 小于10的时候更新 idtext 的组件。

update(['text'], counter < 10);

接下来我们模拟带倒计时的红绿灯来演示GetX 的定向更新的使用。

业务逻辑

为了分别控制红绿灯,我们需要三个组件,分别是红灯、绿灯和黄灯。

image.png

然后是倒计时,我们设置规则如下:

  • 绿灯亮的时长为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<TrafficLightController>(
  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 相关代码

屏幕录制2021-09-07 下午10.13.18.gif

总结

本篇介绍了 GetXGetBuilder使用id参数实现定向刷新的特性。这种情况适用于多个组件共用一个状态对象,但更新条件不同的情况,比如本例的红绿灯。同时,GetxControllerupdate 方法还可以实现条件更新,通过id 加条件组合能够实现更加精准的定向刷新。


我是岛上码农,微信公众号同名,这是Flutter 入门与实战的专栏文章,提供体系化的 Flutter 学习文章。对应源码请看这里:Flutter 入门与实战专栏源码。如有问题可以加本人微信交流,微信号:island-coder

👍🏻:觉得有收获请点个赞鼓励一下!

🌟:收藏文章,方便回看哦!

💬:评论交流,互相进步!