Flutter 局部刷新小组件汇总

466 阅读2分钟
前言:
在Flutter实际的开发工作中,局部刷新也就是更小的颗粒度刷新一直是开发者追求的一个目标,总结一下几个常用的局部刷新的小组件。
首先来说有很多状态管理的框架,可以轻松实现局部刷新。
  1. Provider:使用 Consumer 或者 Selector 来实现。

  2. Bloc:使用 BuildWhen 来实现。

  3. GetX:使用 Obx() 来实现。

  4. RiverPod通过 ref.watch(homeDetailScrollRiverProvider.select 或者 细化拆分river 来实现。

其实,Flutter本身自己也提供了局部刷新的小组件,随着业务的复杂,用这些小组件配合状态管理框架一起来使用,会使颗粒度更细,法力无边。
一. StatefulWidget

在写页面时,尽量的细化和封装组件,小组件继承 StatefulWidget 通过 setStates 来更新小组件自己,页面上其他组件不变。

二. StatefulBuilder

有些时候可能一个小组件或者页面相对复杂,而需要根据状态变化的 Widget 只有一个,要想做到更小颗粒度的刷新那肯定就不能调用 setStates ,因为小组件本身不需要全部重新 build 只是需要重新构建根据状态变化的 Widget 即可。

简单来说,StatefulBuilder 是一个  “便携式”、“嵌入式”的 StatefulWidget, 它的独特之处在于它的 builder 方法,这个方法提供了一个 setState 回调,你可以用这个回调来触发仅重建 StatefulBuilder 内部 UI 的局部刷新。

在实际的项目开发过程中有很多使用的场景,比如 表单选择 点赞 收藏 等 。

写了一个例子,点击 widget 改变自身文字显示 代码如下:

return StatefulBuilder(
  builder: (context, buildSetState) {
    return InkWell(
      onTap: () {
        _title = Until.getTitle();
        buildSetState.call(() {});
      },
      child: Container(
        width: double.infinity,
        color: Colors.white,
        alignment: Alignment.center,
        padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 18.w),
        child: Text(_title),
      ),
    );
  },
);
三. ValueNotifier + ValueListenableBuilder

ValueListenableBuilder 的使用场景和 StatefulBuilder 类似,二者在很多的场景中可以互相替代。

ValueListenableBuilder官方推荐的用于中等复杂度场景的局部刷新方案。它将数据(ValueNotifier)  和UI(ValueListenableBuilder)  分离,实现完美解耦。

极致的局部刷新ValueListenableBuilder 的 builder 方法就是需要刷新的最小范围。

完美解耦:组件之间不需要互相知道对方的存在,它们只通过 ValueNotifier通信。

写了一个例子,点击 widget 改变自身文字显示 代码如下:

Widget _valueNotifierWidget() {
  return ValueListenableBuilder<String>(
    valueListenable: _valueNotifier,
    builder: (context, value, child) {
      return InkWell(
        onTap: () {
          _valueNotifier.value = Until.getTitle();
        },
        child: Container(
          width: double.infinity,
          color: Colors.white,
          alignment: Alignment.center,
          margin: EdgeInsets.symmetric(vertical: 12.h),
          padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 18.w),
          child: Text(value),
        ),
      );
    },
  );
}
三. StreamBuilder

对于异步数据流(比如来自网络、文件读取),用 StreamBuilder 来实现局部刷新也有助于解耦。

写了一个例子,网络请求数据,成功之后更新自身文字展示 代码如下:

Widget _streamBuilderWidget() {
  return StreamBuilder<CarModel?>(
    stream: _carStream,
    builder: (context, snapshot) {
      if (snapshot.hasError) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.error_outline, color: Colors.red, size: 60),
            const SizedBox(height: 16),
            Text(
              '出错啦: ${snapshot.error}',
              style: Theme.of(context).textTheme.bodyLarge,
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 16),
            FilledButton(
              onPressed: _refreshData, // 出错后可以重试
              child: const Text('重试'),
            ),
          ],
        );
      } else if (snapshot.hasData) {
        return Container(
          color: Colors.blue,
          padding: const EdgeInsets.all(8.0),
          child: Text(snapshot.data?.description ?? ""),
        );
      } else {
        return const CircularProgressIndicator();
      }
    },
  );
}
四. FutureBuilder

FutureBuilder 是 Flutter 中用于处理单次异步操作并据此构建 UI 的强大组件。它比 StreamBuilder 更简单,适用于绝大多数“请求-响应”模式的场景,如网络请求、数据库查询、本地文件读取等。

写了一个例子,网络请求数据,成功之后更新自身文字展示 代码如下:

Widget _futureBuilderWidget() {
  return FutureBuilder<CarModel?>(
    future: _futureCar,
    builder: (context, snapshot) {
      if (snapshot.hasError) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.error_outline, color: Colors.red, size: 60),
            const SizedBox(height: 16),
            Text(
              '出错啦: ${snapshot.error}',
              style: Theme.of(context).textTheme.bodyLarge,
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 16),
            FilledButton(
              onPressed: _refreshStream, // 出错后可以重试
              child: const Text('重试'),
            ),
          ],
        );
      }
      if (snapshot.hasData) {
        final CarModel? carModel = snapshot.data;
        return Container(
          color: Colors.red,
          padding: const EdgeInsets.all(8.0),
          child: Text(carModel?.description ?? ''),
        );
      }
      return const CircularProgressIndicator();
    },
  );
}

Demo 地址