当前位置:   article > 正文

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

flutter section header置顶

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

一.先上效果图

在这里插入图片描述

二.列表布局实现

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

_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("曹真")
  ],
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

在这里插入图片描述

三.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();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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

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

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

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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!);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

四.吸顶效果实现

直接把上面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.
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

在这里插入图片描述

仔细看上面的效果,貌似只有第一个Sliver吸顶了,我们把分组item的背景改成透明的,再来看看效果,就知道怎么回事了 本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】

推荐阅读
相关标签