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卡片偏移效果
      • 一、概述
      • 二、研发
        • 基础页面搭建
        • 观察 PageView
        • item 的偏移实现
      • 三、最后
    • Flutter - 轻松搞定炫酷视差(Parallax)效果
    • Flutter - 危!3.24版本苹果审核被拒!
    • Flutter - 子部件任意位置观察滚动数据
    • Flutter - iOS编译加速
    • Flutter - Xcode16 还原编译速度
  • 移动端
  • Flutter开发
LinXunFeng
2024-09-08
目录

Flutter - 轻松实现PageView卡片偏移效果

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

# 一、概述

在前不久的一个需求里,需要在点击地图页上的大头针时,页面底下显示对应的物件卡片,还可以左右切换,并且在切换的过程中需要突出展示当前的物件卡片,如下图所示。

接下来就来讲讲具体实现

# 二、研发

# 基础页面搭建

/// pageView 控制器
late PageController pageController;

/// item 数量
int pageItemCount = 10;

@override
void initState() {
  super.initState();
  pageController = PageController(
    // 初始化页的下标
    initialPage: 4,
    // 每一页占据的视口比例
    viewportFraction: 0.9,
  );
}

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text("PageView"),
    ),
    body: Stack(
      children: [
        // 底部地图
        _buildMap(),
        // PageView,用于显示地图大头针对应的一些物件信息
        Positioned(
          left: 0,
          right: 0,
          bottom: 0,
          child: _buildPageView(),
        ),
      ],
    ),
  );
}

底部的卡片视图是使用 PageView 来实现,具体代码如下所示:

Widget _buildPageView() {
  Widget resultWidget = PageView.builder(
    controller: pageController,
    itemBuilder: (context, index) {
      // 构建 item
      Widget resultWidget = Container(
        decoration: BoxDecoration(
          color: Colors.blue[100],
          borderRadius: BorderRadius.circular(4),
        ),
        alignment: Alignment.center,
        child: Text("Page $index"),
      );
      // 设置 item 的左右边距
      resultWidget = Container(
        margin: const EdgeInsets.symmetric(horizontal: 8),
        child: resultWidget,
      );
      return resultWidget;
    },
    itemCount: pageItemCount,
  );
  // 固定 300 的高度
  resultWidget = SizedBox(
    height: 300,
    child: resultWidget,
  );
  return resultWidget;
}

以上的视图布局对大家来说都很简单,大致看看就可以了,接下来就是如何实现这种偏移突出的效果。

# 观察 PageView

这里使用到的是我写的一个滚动视图观察库 https://github.com/fluttercandies/flutter_scrollview_observer (opens new window) 。

用法很简单:

  1. 使用 ListViewObserver 将 PageView 包裹起来
  2. 设置 triggerOnObserveType 为 .directly 不做显示 item 变化对比,直接将获取到的观察数据返回
  3. 在观察结果回调 onObserve 中,取出 item 的相关数据(下标、可视区域占比等)进行计算
final observerController = ListObserverController();

Widget _buildPageView() {
  Widget resultWidget = PageView.builder(
    ...
  );

  // 使用 ListViewObserver 包裹 PageView 
  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;
        // 计算偏移
        final offsetY = (1 - itemDisplayPercentage) * offsetYDelta;
        pageItemOffsetYList[itemIndex].value = offsetY;
      }
    },
    // 指定需要观察的 RenderSliver 对象
    customTargetRenderSliverType: (renderObj) {
      return renderObj is RenderSliverFillViewport;
    },
  );
  
  ...
  return resultWidget;
}

triggerOnObserveType 和 customTargetRenderSliverType 在之前的【Flutter - 船新升级😱支持观察第三方构建的滚动视图💪】一文中有详细的说明,这里就不再赘述。

这里重要说明一下,ListViewObserver 默认是处理 ListView 和 SliverList 的,但并不表示它只能处理它们,只要内部的树结构一致就可以处理,如果结构一致但 RenderSliver 与 SliverList 的不同,则可以通过 customTargetRenderSliverType 指定相应的 RenderSliver 类型即可,就比如这里的 PageView,其 RenderSliver 的类型为 RenderSliverFillViewport。

在这里附上 PageView 和 ListView 的树结构截图,看最右侧的 Widget Details Tree,看完应该就能感受到什么叫结构一致了。

PageView 结构

ListView 结构

# item 的偏移实现

// item 的最大偏移量
double offsetYDelta = 50;
// 记录 item 的偏移量
List<ValueNotifier<double>> pageItemOffsetYList = [];

@override
void initState() {
  super.initState();
  ...
  // 初始化
  pageItemOffsetYList = List.generate(
    pageItemCount,
    (index) {
      return ValueNotifier<double>(0);
    },
  );
  
  // 延迟 100ms,触发一次观察
  Future.delayed(const Duration(milliseconds: 100)).then((_) {
    observerController.dispatchOnceObserve();
  });
}

Widget _buildPageView() {
  Widget resultWidget = PageView.builder(
    controller: pageController,
    itemBuilder: (context, index) {
      Widget itemWidget = Container(
        ...
      );
      
      // item 局部刷新
      Widget resultWidget = ValueListenableBuilder(
        valueListenable: pageItemOffsetYList[index],
        builder: (BuildContext context, double offsetY, Widget? child) {
          // 偏移量发生变化
          return Transform.translate(
            offset: Offset(0, offsetY),
            child: itemWidget,
          );
        },
      );
      
      resultWidget = Container(
        ...
      );
      return resultWidget;
    },
    itemCount: pageItemCount,
  );

  resultWidget = ListViewObserver(
    ...
    onObserve: (resultModel) {
      final displayingChildModelList = resultModel.displayingChildModelList;
      for (var itemModel in displayingChildModelList) {
        // 取出 item 的下标
        final itemIndex = itemModel.index;
        // 取出 item 的可视区域占比
        final itemDisplayPercentage = itemModel.displayPercentage;
        // 计算偏移
        // item 的【可视占比区间】与【偏移区间】两者间的区间关系如下
        // 可视占比区间: [0, 1]
        // 偏移区间: [50, 0]
        final offsetY = (1 - itemDisplayPercentage) * offsetYDelta;
        // 更新当前 item 的偏移量
        pageItemOffsetYList[itemIndex].value = offsetY;
      }
    },
    ...
  );
  
  resultWidget = SizedBox(
    height: 300,
    child: resultWidget,
  );
  return resultWidget;
}

关键变量说明

  • offsetYDelta: item 的最大偏移量,这里是 50
  • pageItemOffsetYList: 记录各个 item 的当前偏移量

更新 item 偏移说明

  • 结合 pageItemOffsetYList + ValueListenableBuilder 来局部刷新 item
  • 在 onObserve 中计算 item 的偏移量,并更新 pageItemOffsetYList 中对应下标的值
  • item 的【可视占比区间 ([0, 1])】与【偏移区间 ([50, 0])】成反比,所以用 1 减去 itemDisplayPercentage 成正向比例,再去乘以 offsetYDelta 算出偏移

# 三、最后

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

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

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

#Dart#Flutter
Flutter - 升级到3.24后页面还会多次rebuild吗?🧐
Flutter - 轻松搞定炫酷视差(Parallax)效果

← Flutter - 升级到3.24后页面还会多次rebuild吗?🧐 Flutter - 轻松搞定炫酷视差(Parallax)效果→

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