FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

    • Dart - 抽象类的实例化
    • Flutter - 打印好用的Debug日志
    • Flutter - 混合开发
    • Flutter - 解决混合开发iOS脚本打包遇到的问题
    • Flutter - 低版本在iOS14上遇到的问题与解决方案
    • Flutter - 解决原生弹窗的触摸事件被Flutter响应的问题
    • Flutter - 实现列表上下拉切换header
    • Flutter - 获取ListView当前正在显示的Widget信息
    • Flutter - 列表滚动定位超强辅助库,墙裂推荐!🔥
    • Flutter - 快速实现聊天会话列表的效果,完美💯
    • Flutter - 聊天输入框更新文本时的必备优化点🔖
    • Flutter - 我给官方提PR,解决run命令卡住问题 😃
    • Flutter - 探索run命令到底做了什么 🤔
    • Flutter - 引擎调试(iOS篇)🛠
    • Flutter - 引擎调试bug到提交PR实战 🐞
    • Flutter - 船新升级😱支持观察第三方构建的滚动视图💪
    • Flutter - 瀑布流交替播放视频 🎞
    • Flutter - IM保持消息位置大升级(支持ChatGPT生成式消息) 🤖
    • Flutter - 滚动视图中的表单防遮挡 🗒
    • Flutter - 秒杀1/2曝光统计 📊
    • 一天内加入 Flutter 和 FlutterCandies 两大组织是什么体验 🧐
    • Flutter - 如何快速搓一个微信通讯录列表(azlist) 📓
    • Flutter - 混编项目集成Shorebird热更新🐦(安卓篇)
    • Flutter - 混编项目集成Shorebird热更新🐦(iOS篇)
    • Flutter - 解决返回原生页面时dispose方法未被触发的问题 🐞
    • Flutter - 升级3.19之后页面多次rebuild?🤨
    • Flutter - 热更新 Shorebird 1.0 正式版来了 🐦
    • Flutter - 使用Pigeon实现视频缓存插件 🐌
    • Flutter - 轻松搞定屏幕旋转功能 😎
    • Flutter - 解决Connection closed before full header was received
    • Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
    • Flutter - 支持观察NestedScrollView,兼容性更强 😈
    • Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻
    • Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
    • Flutter - 轻松实现PageView卡片偏移效果
    • Flutter - 轻松搞定炫酷视差(Parallax)效果
    • Flutter - 危!3.24版本苹果审核被拒!
    • Flutter - 子部件任意位置观察滚动数据
      • 一、概述
      • 二、实战
        • Page
        • Item
      • 三、其它说明
        • of
        • maybeOf
        • 嵌套问题
      • 四、最后
    • Flutter - iOS编译加速
    • Flutter - Xcode16 还原编译速度
  • 移动端
  • Flutter开发
LinXunFeng
2024-11-24
目录

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

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

# 一、概述

在 scrollview_observer (opens new window) 的 1.23.0 中,新增了允许对观察结果进行监听的功能,也就是说你可以不需要在固定的观察结果回调(如:onObserve、onObserveAll )中去处理数据了。

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

# 二、实战

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

# Page

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

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 提供数据。

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,并传入下标和对应的数据

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 来控制背景的对齐偏移,进而实现视差效果。

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 中对观察结果数据的监听。

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,如果找不到会抛异常。

observerState = ListViewObserver.of(context);
observerState = GridViewObserver.of(context);
observerState = SliverViewObserver.of(context);

# maybeOf

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

observerState = ListViewObserver.maybeOf(context);
observerState = GridViewObserver.maybeOf(context);
observerState = SliverViewObserver.maybeOf(context);

# 嵌套问题

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

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 所包裹。

如果此时我们直接使用 item 的 BuildContext 去调用 of,则会拿到离它最近的 tag2 的 ListViewObserverState。

// tag2 的 ListViewObserverState
ListViewObserver.of(itemContext)

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

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

ListViewObserver.of(
  context,
  tag: tag1,
);

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

ListViewObserver.of(tag2Context);

# 四、最后

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

GitHub: https://github.com/fluttercandies/flutter_scrollview_observer (opens new window)

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

#Dart#Flutter
Flutter - 危!3.24版本苹果审核被拒!
Flutter - iOS编译加速

← Flutter - 危!3.24版本苹果审核被拒! Flutter - iOS编译加速→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×