02_Flutter自定义Sliver组件实现分组列表吸顶效果

news2025/1/7 20:58:10

02_Flutter自定义Sliver组件实现分组列表吸顶效果

一.先上效果图

在这里插入图片描述

二.列表布局实现

比较简单,直接上代码,主要使用CustomScrollView和SliverToBoxAdapter实现

_buildSection(String title) {
  return SliverToBoxAdapter(
    child: RepaintBoundary(
      child: Container(
        height: 50,
        color: Colors.brown,
        alignment: Alignment.center,
        child: Text(title),
      ),
    )
  );
}

_buildItem(String title) {
  return SliverToBoxAdapter(
    child: RepaintBoundary(
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 15),
        height: 70,
        color: Colors.cyanAccent,
        alignment: Alignment.centerLeft,
        child: Text(title),
      ),
    )
  );
}

CustomScrollView(
  slivers: [
    _buildSection("蜀汉五虎将"),
    _buildItem("关羽"),
    _buildItem("张飞"),
    _buildItem("赵云"),
    _buildItem("马超"),
    _buildItem("黄忠"),

    _buildSection("虎贲双雄"),
    _buildItem("许褚"),
    _buildItem("典韦"),

    _buildSection("五子良将"),
    _buildItem("张辽"),
    _buildItem("乐进"),
    _buildItem("于禁"),
    _buildItem("张郃"),
    _buildItem("徐晃"),

    _buildSection("八虎骑"),
    _buildItem("夏侯惇"),
    _buildItem("夏侯渊"),
    _buildItem("曹仁"),
    _buildItem("曹纯"),
    _buildItem("曹洪"),
    _buildItem("曹休"),
    _buildItem("夏侯尚"),
    _buildItem("曹真")
  ],
)

在这里插入图片描述

三.SliverToBoxAdapter和SliverPersistentHeader

可以使用Flutter提供的SliverPersistentHeader组件实现,在使用SliverPersistentHeader时要求我们明确指定子控件的高度,不支持吸顶上推效果,使用起来不够灵活,所以我们参考并结合SliverToBoxAdapter和SliverPersistentHeader源码,自己实现一个自适应高度的吸顶Sliver组件,并在此基础上一步步实现吸顶上推效果。

  • 编写StickySliverToBoxAdapter类,继承自SingleChildRenderObjectWidget
class StickySliverToBoxAdapter extends SingleChildRenderObjectWidget {

  const StickySliverToBoxAdapter({
    super.key,
    super.child
  });

  
  RenderObject createRenderObject(BuildContext context) => _StickyRenderSliverToBoxAdapter();

}

SingleChildRenderObjectWidget类要求我们自己实现createRenderObject方法,返回一个RenderObject对象,而对于一个S liver组件而言,这个RenderObject必须是RenderSilver的子类。

  • 编写_StickyRenderSliverToBoxAdapter,继承RenderSliverSingleBoxAdapter
class _StickyRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {

  
  void performLayout() {
    // TODO: implement performLayout
  }

}

RenderSliverSingleBoxAdapter要求子类实现performLayout方法,performLayout会对widegt的布局和绘制做控制,实现吸顶效果的关键就在于performLayout方法的实现。先依次看下SliverToBoxAdapter和SliverPersistentHeader对应RenderObject的performLayout相关方法的实现。

  • RenderSliverToBoxAdapter#performLayout

void performLayout() {
  if (child == null) {
    geometry = SliverGeometry.zero;
    return;
  }
  final SliverConstraints constraints = this.constraints;
  //摆放子View,并把constraints传递给子View
  child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
  //获取子View在滑动主轴方向的尺寸
  final double childExtent;
  switch (constraints.axis) {
    case Axis.horizontal:
      childExtent = child!.size.width;
    case Axis.vertical:
      childExtent = child!.size.height;
  }
  final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
  final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);

  assert(paintedChildSize.isFinite);
  assert(paintedChildSize >= 0.0);
  //更新SliverGeometry
  geometry = SliverGeometry(
    scrollExtent: childExtent,
    paintExtent: paintedChildSize,
    cacheExtent: cacheExtent,
    maxPaintExtent: childExtent,
    hitTestExtent: paintedChildSize,
    hasVisualOverflow: childExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0,
  );
  //更新paintOffset,由滑动偏移量constraints.scrollOffset决定
  setChildParentData(child!, constraints, geometry!);
}
  • RenderSliverFloatingPersistentHeader#performLayout

SliverPersistentHeader的performLayout方法中调用了updateGeometry方法去更新geometry,而吸顶的关键就在updateGeometry方法中,也就是paintOrigin的值。constraints.overlap的值代表前一个Sliver和当前Sliver被覆盖部分的高度。


double updateGeometry() {
  final double minExtent = this.minExtent;
  final double minAllowedExtent = constraints.remainingPaintExtent > minExtent ?
    minExtent :
  constraints.remainingPaintExtent;
  final double maxExtent = this.maxExtent;
  final double paintExtent = maxExtent - _effectiveScrollOffset!;
  final double clampedPaintExtent = clampDouble(paintExtent,
                                                minAllowedExtent,
                                                constraints.remainingPaintExtent,
                                               );
  final double layoutExtent = maxExtent - constraints.scrollOffset;
  final double stretchOffset = stretchConfiguration != null ?
    constraints.overlap.abs() :
  0.0;
  geometry = SliverGeometry(
    scrollExtent: maxExtent,
    paintOrigin: math.min(constraints.overlap, 0.0),
    paintExtent: clampedPaintExtent,
    layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent),
    maxPaintExtent: maxExtent + stretchOffset,
    maxScrollObstructionExtent: minExtent,
    hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
  );
  return 0.0;
}

四.吸顶效果实现

直接把上面updateGeometry中设置SliverGeometry的代码拷贝到_StickyRenderSliverToBoxAdapter#performLayout实现中,maxExtent和minExtent这两个值是由SliverPersistentHeader传入的SliverPersistentHeaderDelegate对象提供的。这里可以自己去看SliverPersistentHeaderDelegate的源码,就不多废话了。我们只需要把maxExtent和minExtent这两个值都改为子控件在主轴方向的尺寸大小即可。

 _buildSection(String title) {
   return StickySliverToBoxAdapter(
       child: RepaintBoundary(
         child: Container(
           height: 50,
           color: Colors.brown,
           alignment: Alignment.center,
           child: Text(title),
         ),
       )
   );
 }

class _StickyRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {

  
  void performLayout() {
    if (child == null) {
      geometry = SliverGeometry.zero;
      return;
    }
    final SliverConstraints constraints = this.constraints;
    //摆放子View,并把constraints传递给子View
    child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
    //获取子View在滑动主轴方向的尺寸
    final double childExtent;
    switch (constraints.axis) {
      case Axis.horizontal:
        childExtent = child!.size.width;
      case Axis.vertical:
        childExtent = child!.size.height;
    }

    final double minExtent = childExtent;
    final double minAllowedExtent = constraints.remainingPaintExtent > minExtent ?
    minExtent : constraints.remainingPaintExtent;
    final double maxExtent = childExtent;
    final double paintExtent = maxExtent;
    final double clampedPaintExtent = clampDouble(paintExtent,
      minAllowedExtent,
      constraints.remainingPaintExtent,
    );
    final double layoutExtent = maxExtent - constraints.scrollOffset;

    geometry = SliverGeometry(
      scrollExtent: maxExtent,
      paintOrigin: min(constraints.overlap, 0.0),
      paintExtent: clampedPaintExtent,
      layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent),
      maxPaintExtent: maxExtent,
      maxScrollObstructionExtent: minExtent,
      hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
    );
  }
}

在这里插入图片描述

仔细看上面的效果,貌似只有第一个Sliver吸顶了,我们把分组item的背景改成透明的,再来看看效果,就知道怎么回事了😄。

在这里插入图片描述

可以看到,所有的分组section都已经吸顶了,只不过吸顶位置都是0,并且前一个section把后一个section覆盖了,我们下一步实现上推功能后,这个问题自热而然的就解决了。

五.实现上推效果

在这里插入图片描述

如图,当前section与前一个section重合了多少,前一个section就往上移动多少,也就是移动constraints.overlap即可,往下滑动也是同样的道理。

//查找前一个吸顶的section
RenderSliver? _prev() {
  if(parent is RenderViewportBase) {
    RenderSliver? current = this;
    while(current != null) {
      current = (parent as RenderViewportBase).childBefore(current);
      if(current is _StickyRenderSliverToBoxAdapter && current.geometry != null) {
        return current;
      }
    }
  }
  return null;
}


void performLayout() {
  if (child == null) {
    geometry = SliverGeometry.zero;
    return;
  }
  final SliverConstraints constraints = this.constraints;
  //摆放子View,并把constraints传递给子View
  child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);
  //获取子View在滑动主轴方向的尺寸
  final double childExtent;
  switch (constraints.axis) {
    case Axis.horizontal:
      childExtent = child!.size.width;
    case Axis.vertical:
      childExtent = child!.size.height;
  }

  final double minExtent = childExtent;
  final double minAllowedExtent = constraints.remainingPaintExtent > minExtent ?
    minExtent : constraints.remainingPaintExtent;
  final double maxExtent = childExtent;
  final double paintExtent = maxExtent;
  final double clampedPaintExtent = clampDouble(paintExtent,
                                                minAllowedExtent,
                                                constraints.remainingPaintExtent,
                                               );
  final double layoutExtent = maxExtent - constraints.scrollOffset;

  geometry = SliverGeometry(
    scrollExtent: maxExtent,
    paintOrigin: min(constraints.overlap, 0.0),
    paintExtent: clampedPaintExtent,
    layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent),
    maxPaintExtent: maxExtent,
    maxScrollObstructionExtent: minExtent,
    hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity.
  );

  //上推关键代码: 当前吸顶的Sliver被覆盖了多少,前一个吸顶的Sliver就移动多少
  RenderSliver? prev = _prev();
  if(prev != null && constraints.overlap > 0) {
    setChildParentData(_prev()!, constraints.copyWith(scrollOffset: constraints.overlap), _prev()!.geometry!);
  }
}

搞定,可以洗洗睡了,嘿嘿。

在这里插入图片描述

六.Fixed: 吸顶section点击事件失效

重写childMainAxisPosition方法返回0即可

class _StickyRenderSliverToBoxAdapter extends RenderSliverSingleBoxAdapter {

 	...
  
  // 必须重写,否则点击事件失效。
  
  double childMainAxisPosition(covariant RenderBox child) => 0.0;
  
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/999695.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Doodles版洞洞鞋3天售罄 蓝筹NFT卖货自救

加密资产市场仍在寒冬,上个牛市爆火的NFT正在经历第一次深熊,无数NFT项目遭市场清洗,玩家争相出逃。市场冷清到惨烈,哪怕是价值认可度最高的蓝筹NFT,也未能逃过持续暴跌的窘境,一些项目开始自救。 近期&am…

C# 反射机制

图片来自:https://www.cnblogs.com/tangge/p/3440605.html

体育场馆LED显示屏的分类及应用

体育场馆LED显示屏是用于体育场馆、体育赛事和体育娱乐活动的重要设备,它们具有不同的分类和应用。免费为你提供体育馆LED屏幕指南。 以下是体育场馆LED显示屏的一些常见分类和应用: 按用途分类: 比赛信息显示屏: 用于显示比赛的得…

Unit 2 uni-app入门

1 uni-app 介绍 1.1 什么是uni-app uni-app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到Android、iOS、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/飞书/QQ/快手/钉钉/淘宝&…

[框架设计之道(二)]设备、任务设置及业务流程

[框架设计之道&#xff08;二&#xff09;]设备、任务设置及业务流程 说明 此文档是开发中对设备设置项的管理。因为硬件在使用的过程中涉及大量设置项&#xff0c;因此需要单独开一篇文档说明设备的设置和任务的设置。 一、设备设置 1.基础接口 /// <summary> /// 配置…

Python有向图从起点到终点遍历所有路径

参考文章&#xff1a;https://blog.csdn.net/weixin_39797176/article/details/121776940 输入数据说明&#xff1a; graph[1] [2, 3] 表示顶点1有边指向顶点2和3&#xff0c;将所有的边录入start(1,8)表示遍历从顶点1到顶点8的所有路径 graph {} allpaths [] solopath …

空气减压阀QAR2000-02 QAR2500-02 QAR2500-03 QAR3000-02

空气减压阀QAR1000-M5、QAR2000-01、QAR2000-02、QAR2500-02、QAR2500-03、QAR3000-02、QAR3000-03、QAR4000-03、QAR4000-04、QAR4000-06、QAR5000-06、QAR5000-10、QPR2000-01、QPR2000-02、XR4&#xff0c;空气过滤减压阀QPW2000-01、QPW2000-02、XFRU4&#xff0c;精密减压…

C# Onnx Yolov8 Seg 分割

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System…

数字孪生警务3D可视化系统有哪些作用?

数字孪生警务3D可视化系统是利用先进的数字技术和模拟技术&#xff0c;将真实的公安运行环境、设施和人员以虚拟的形式呈现出来的系统。它可以模拟公安部门的各种工作场景&#xff0c;为公安人员提供更直观、全面的信息&#xff0c;并发挥以下作用&#xff1a; 一、仿真演练和培…

第三章 Linux文件系统

第三章 Linux文件系统 ​ 文件系统是操作系统用于确定磁盘或分区上的文件的方法和数据结构&#xff0c;即文件在磁盘上的组织方法。文件系统由3部分组成&#xff1a;与文件管理有关的软件、被管理的文件以及实施文件管理所需的数据结构。 1.Ubuntu的文件系统 ​ 文件系统负责…

AI是风口还是泡沫?

KlipC报道&#xff1a;狂热的人工智能追捧潮有所冷静&#xff0c;投资者在“上头”的追涨之后&#xff0c;开始回归到对基本面的关注。 KlipC的合伙人Andi D表示&#xff1a;“近日&#xff0c;有关英伟达二季度“破纪录”财报涉嫌造假的话题正在社交媒体和投资者论坛中甚嚣尘上…

【转存】从 JMM 透析 volatile 与 synchronized 原理

在面试、并发编程、一些开源框架中总是会遇到 volatile 与 synchronized 。synchronized 如何保证并发安全&#xff1f;volatile 语义的内存可见性指的是什么&#xff1f;这其中又跟 JMM 有什么关系&#xff0c;在并发编程中 JMM 的作用是什么&#xff0c;为什么需要 JMM&#…

java spring cloud 企业电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展

项目说明 随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大&#xff0c;公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境&#xff0c;最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范&#xff0c;以及审…

肖sir__项目管理工具之禅道__013

项目管理工具之禅道 一、项目管理工具介绍 1、禅道是一个项目管理软件&#xff0c;它是易软天创公司&#xff0c;为了解决众多企业在管理中出现混乱&#xff0c;无序的现象&#xff0c;开发出来的 它是基于产品管理&#xff0c;项目管理&#xff0c;测试管理于一身&#xff0c…

同城线下约玩陪玩系统线下达人系统源码

结构:TINKPHP框架公众号H5;系统开源,方便二次开发 编译:前端使用UNIAPP开发,可快速编译成APP及微信小程序和公众号H5组件:ICONFONT-UI,基于阿里图库团队UI库,用户体验棒 终端:前后端分离开发模式&#xff0c;开发更清晰分工更明确、提升开发效率、前端使用UNIAPP可快速编译各类…

[Linux 基础] linux基础指令(1)

文章目录 1、Linux下基本指令1.ls指令2.pwd指令3.cd指令4.touch指令5.mkdir指令6.rmdir指令 && rm指令7.man指令8.cp指令9.mv指令10.cat指令11.more指令12.less指令 Linux学习笔记从今天开始不断更新了。第一篇我们从基础指令开始学起。 1、Linux下基本指令 好多人都说…

Swift页面添加水印

本文主要讨论的是给图片或者视图添加全屏水印。比较常见的是添加单个水印,这个比较好处理,网络上也有很多参考的方法。本文实现的是铺满的全屏水印,具体参考效果如下: 实现思路: 1、根据水印文本以及相应样式生成水印图片,水印图大小根据文本计算而来 2、生成需要铺满水…

TortoiseSVN 详细操作指南

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 热爱技术的小郑 1、引言 考虑以下几种情况&#xff1a; 你是否在一个…

高压放大器在静电喷涂技术中的应用

高压放大器静电喷涂作为一种表面处理技术&#xff0c;在工业生产和制造领域中得到了广泛应用。然而&#xff0c;在实际的生产应用过程中&#xff0c;高压放大器静电喷涂也面临着生产效率不高、成本过高等问题。因此&#xff0c;如何提高生产效率和降低成本已经成为行业内关注的…

如何在Windows 10/11中重置网络,以及重置后的注意事项有哪些

本文介绍如何在Windows 10和Windows 11中重置网络设置。 如何重置Windows 10网络设置 在Windows10中使用网络重置实用程序相当简单。 一、进入“开始”菜单>“设置”,然后选择“网络和Internet”。 二、在左侧导航窗格中,选择“状态”以确保你正在查看网络状态窗口。然…