Flutter - 子部件任意位置观察滚动数据

欢迎关注微信公众号:FSA全栈行动 👋

一、概述

scrollview_observer1.23.0 中,新增了允许对观察结果进行监听的功能,也就是说你可以不需要在固定的观察结果回调(如:onObserveonObserveAll )中去处理数据了。

那具体是什么意思呢?我们通过实战来了解一下。

二、实战

这里以之前的 Flutter - 轻松搞定炫酷视差(Parallax)效果 为例

Page

这里附上原先 PageView 的构建代码,以及使用 ListViewObserver 对其进行观察,并在 onObserve 回调中实现 Parallax 效果的详细处理逻辑

dart 复制代码
final observerController = ListObserverController();

Widget _buildPageView() {
  Widget resultWidget = PageView.builder(
    ...
  );
  resultWidget = ListViewObserver(
    controller: observerController,
    child: resultWidget,
    triggerOnObserveType: ObserverTriggerOnObserveType.directly,
    onObserve: (resultModel) {
      final displayingChildModelList = resultModel.displayingChildModelList;
      for (var itemModel in displayingChildModelList) {
        // 取出 item 的下标
        final itemIndex = itemModel.index;
        // 取出 item 自身的显示占比
        final itemDisplayPercentage = itemModel.displayPercentage;

        // 计算无符号的 alignment.x
        double itemAlignmentX = 1 - itemDisplayPercentage;
        
        // 计算有符号的 alignment.x
        if (itemModel.leadingMarginToViewport > 0) {
          itemAlignmentX = -itemAlignmentX;
        }
        
        // 取值范围判断
        if (itemAlignmentX > 1) {
          itemAlignmentX = 1;
        } else if (itemAlignmentX < -1) {
          itemAlignmentX = -1;
        }
        
        // 赋值
        pageItemBgPicAlignmentXList[itemIndex].value = itemAlignmentX;
      }
    },
    customTargetRenderSliverType: (renderObj) {
      return renderObj is RenderSliverFillViewport;
    },
  );

  ...
  return resultWidget;
}

在上代码中,我们在 onObserve 回调中去处理了 Parallax 效果的一切逻辑,但假设 item 有多种类型呢?比如可能会在其中插一个不需要 Parallax 效果的纯广告图片的 item,这个时候就得去取出对应下标的数据,判断是否需要该效果,否则就 return 不做处理。

那此时就会想,我们能不能仅在需要 Parallax 效果的 item 内部去做呢?

可以,我们先将 onObserve 回调去除,保留 ListViewObserver 及其配置,即保留观察能力,为 item 提供数据。

dart 复制代码
final observerController = ListObserverController();

Widget _buildPageView() {
  Widget resultWidget = PageView.builder(
    ...
  );
  resultWidget = ListViewObserver(
    controller: observerController,
    child: resultWidget,
    triggerOnObserveType: ObserverTriggerOnObserveType.directly,
    customTargetRenderSliverType: (renderObj) {
      return renderObj is RenderSliverFillViewport;
    },
  );

  ...
  return resultWidget;
}

Item

item 抽成 StatefulWidget,并传入下标和对应的数据

dart 复制代码
class ParallaxItemView extends StatefulWidget {
  final int index;
  final String imgUrl;

  const ParallaxItemView({
    Key? key,
    required this.index,
    required this.imgUrl,
  }) : super(key: key);

  @override
  State<ParallaxItemView> createState() => _ParallaxItemViewState();
}

附上视图布局代码,主要靠 picAlignmentX 来控制背景的对齐偏移,进而实现视差效果。

dart 复制代码
class _ParallaxItemViewState extends State<ParallaxItemView> {
  final picAlignmentX = ValueNotifier<double>(0);
  ...

  @override
  void dispose() {
    ...
    picAlignmentX.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    Widget resultWidget = Stack(
      alignment: AlignmentDirectional.center,
      children: [
        Positioned(
          left: 0,
          right: 0,
          top: 0,
          bottom: 0,
          child: _buildPageItemBgPicView(widget.index),
        ),
        ...
      ],
    );
    ...
    return resultWidget;
  }

  Widget _buildPageItemBgPicView(int index) {
    return ValueListenableBuilder(
      valueListenable: picAlignmentX,
      builder: (BuildContext context, double alignmentX, Widget? child) {
        return Image.network(
          widget.imgUrl,
          fit: BoxFit.cover,
          alignment: Alignment(alignmentX, 0),
        );
      },
    );
  }
}

好了,现在来实现在 item 中对观察结果数据的监听。

dart 复制代码
ListViewObserverState? observerState;

@override
void didChangeDependencies() {
  super.didChangeDependencies();

  removeListener();
  // 通过当前 item 的 context 找到 ListViewObserver 的 State
  // 再调用 addListener 方法对其进行监听
  observerState = ListViewObserver.of(context)
    ..addListener(
      onObserve: handleObserverResult,
    );
}

@override
void dispose() {
  ...
  removeListener();
  super.dispose();
}

/// 移除监听
void removeListener() {
  observerState?.removeListener(
    onObserve: handleObserverResult,
  );
  observerState = null;
}

/// 处理监听结果
void handleObserverResult(
  ListViewObserveModel result,
) {
  if (result.displayingChildModelMap.isEmpty) return;
  // 根据 index 取出对应的观察结果数据
  final model = result.displayingChildModelMap[widget.index];
  // 取不到说明当前不在显示区内,重置 picAlignmentX
  if (model == null) {
    picAlignmentX.value = 0;
    return;
  }

  // 计算无符号的 alignment.x
  picAlignmentX.value = 1 - model.displayPercentage;

  // 计算有符号的 alignment.x
  if (model.leadingMarginToViewport > 0) {
    picAlignmentX.value = -picAlignmentX.value;
  }

  // 取值范围判断
  if (picAlignmentX.value > 1) {
    picAlignmentX.value = 1;
  } else if (picAlignmentX.value < -1) {
    picAlignmentX.value = -1;
  }
}

其实很简单,也就三步走

  • 调用 addListener 对观察结果进行监听
  • 处理观察结果数据
  • dispose 方法中调用 removeListener 移除监听

handleObserverResult 方法中关于 picAlignmentX 的计算在之前的 Flutter - 轻松搞定炫酷视差(Parallax)效果 一文中有详细讲解,这里就不再赘述。

三、其它说明

of

根据 context 向上找最近的 ObserverWidgetState,如果找不到会抛异常。

dart 复制代码
observerState = ListViewObserver.of(context);
observerState = GridViewObserver.of(context);
observerState = SliverViewObserver.of(context);

maybeOf

of 的可选类型版本,如果向上找不到对应类型的 ObserverWidgetState 则返回 null

dart 复制代码
observerState = ListViewObserver.maybeOf(context);
observerState = GridViewObserver.maybeOf(context);
observerState = SliverViewObserver.maybeOf(context);

嵌套问题

ObserverWidget 是可以嵌套使用的,如下代码所示

dart 复制代码
widget = getListView(
  scrollController: scrollController,
  itemCount: 100,
  itemBuilder: (context, index) {
    ...
  },
);
widget = ListViewObserver(
  tag: tag2,
  child: widget,
  controller: observerController2,
);
widget = ListViewObserver(
  tag: tag1,
  child: widget,
  controller: observerController1,
);

一个 ListView 被两个 ListViewObserver 所包裹。

如果此时我们直接使用 itemBuildContext 去调用 of,则会拿到离它最近的 tag2ListViewObserverState

dart 复制代码
// tag2 的 ListViewObserverState
ListViewObserver.of(itemContext)

那如果我们想要拿到的是 tag1ListViewObserverState,该怎么做呢?

很简单,使用 tag 参数即可~

dart 复制代码
ListViewObserver.of(
  context,
  tag: tag1,
);

当然,你不使用 tag 参数,而是通过 tag2ListViewObserver 对应的 BuildContext 去调用 of 也是可以的~

dart 复制代码
ListViewObserver.of(tag2Context);

四、最后

通过上述示例的讲解,相信你对 scrollview_observer 的使用又更加清楚,开源不易,如果你也觉得这个库好用,请不吝给个 Star 👍 ,并多多支持!

GitHub: github.com/fluttercand...

本篇到此结束,感谢大家的支持,我们下次再见! 👋


系列文章

  1. Flutter - 获取ListView当前正在显示的Widget信息
  2. Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
  3. Flutter - 快速实现聊天会话列表的效果,完美💯
  4. Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
  5. Flutter - 瀑布流交替播放视频 🎞
  6. Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
  7. Flutter - 滚动视图中的表单防遮挡 🗒
  8. Flutter - 秒杀1/2曝光统计 📊
  9. Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
  10. Flutter - 支持观察NestedScrollView,兼容性更强 😈
  11. Flutter - 轻松实现PageView卡片偏移效果
  12. Flutter - 轻松搞定炫酷视差(Parallax)效果

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有 iOS 技术,还有 AndroidFlutterPython 等文章, 可能有你想要了解的技能知识点哦~

相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
一只大侠的侠6 小时前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
renke33649 小时前
Flutter for OpenHarmony:色彩捕手——基于HSL色轮与感知色差的交互式色觉训练系统
flutter
猫头虎9 小时前
如何排查并解决项目启动时报错Error encountered while processing: java.io.IOException: closed 的问题
java·开发语言·jvm·spring boot·python·开源·maven
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端