首页 > 其他分享 >flutter 效果实现 —— sliver 固定

flutter 效果实现 —— sliver 固定

时间:2022-08-19 18:24:58浏览次数:71  
标签:paintOffset pinnedOffset offset childExtent 固定 child sliver flutter constraints

效果:

说明:绿色块在向上滑动,距离顶部 103 的高度(即 AppBar 下面)时固定

代码:

class PinnedSliverPage extends StatefulWidget {
  const PinnedSliverPage({Key? key}) : super(key: key);

  @override
  State<PinnedSliverPage> createState() => _PinnedSliverPageState();
}

class _PinnedSliverPageState extends State<PinnedSliverPage> {
  @override
  Widget build(BuildContext context) {
    print('topBar: ${MediaQuery.of(context).padding.top}');
    return Scaffold(
        backgroundColor: Colors.blueGrey,
        body: CustomScrollView(
          physics:
              AlwaysScrollableScrollPhysics(parent: BouncingScrollPhysics()),
          slivers: [
            SliverAppBar(
              title: Text("Pinned Sliver"),
              pinned: true,
              backgroundColor: Colors.orange.withOpacity(0.8),
            ),
            SliverToBoxAdapter(
              child: Container(
                alignment: Alignment.center,
                height: 200,
                color: Colors.yellow,
                child: Text("200"),
              ),
            ),
            //距离顶部 103时,开始固定
            SliverPinHeader(
              pinnedOffset: 103,
              child: Container(
                alignment: Alignment.center,
                color: Colors.green.withOpacity(0.8),
                height: 300,
                child: Text("300"),
              ),
            ),
            SliverToBoxAdapter(
              child: Container(
                alignment: Alignment.center,
                height: 1200,
                color: Colors.red,
                child: Text("1200"),
              ),
            )
          ],
        ));
  }
}

class SliverPinHeader extends SingleChildRenderObjectWidget {
  const SliverPinHeader({
    Key? key,
    required this.pinnedOffset,
    required Widget child,
  }) : super(child: child);

  final double pinnedOffset;

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderSliverPinHeader(pinnedOffset: pinnedOffset);
  }

  @override
  void updateRenderObject(BuildContext context, RenderSliverPinHeader renderObject) {
    renderObject.pinnedOffset = pinnedOffset;
  }
}

class RenderSliverPinHeader extends RenderSliverSingleBoxAdapter {
  RenderSliverPinHeader({required double pinnedOffset}): _pinnedOffset = pinnedOffset;

  late double _pinnedOffset = 0;

  set pinnedOffset(double value) {
    if (_pinnedOffset == value) {
      return;
    }
    _pinnedOffset = value;
    markNeedsLayout();
  }

  @override
  void setupParentData(RenderObject child) {
    if (child.parentData is! SliverPhysicalParentData) {
      child.parentData = SliverPhysicalParentData();
    }
  }

  @override
  void performLayout() {
    if (child == null) {
      geometry = SliverGeometry.zero;
      return;
    }
    // 1.对子节点进行约束 SliverConstraints
    child!.layout(constraints.asBoxConstraints(), parentUsesSize: true);

    double childExtent;
    switch (constraints.axis) {
      case Axis.horizontal:
        childExtent = child!.size.width;
        break;
      case Axis.vertical:
        childExtent = child!.size.height;
        break;
    }

    final paintedChildSize =
        calculatePaintOffset(constraints, from: 0.0, to: childExtent);

    final double cacheExtent =
        calculateCacheOffset(constraints, from: 0.0, to: childExtent);

    // 2.上报当前节点的布局信息给 viewport
    geometry = SliverGeometry(
      // 默认 paintExtent 大于 0时才显示当前 sliver。手动设置 visible: true 则总是可见,不受 paintExtent 影响。
      // visible: paintExtent > 0.0
      // 可以调整此值为 -constraints.scrollOffset,来影响 paintChild.offset。
      // paintOrigin: 0
      // 通常等于子组件的高度,并且不随着滚动而改变
      scrollExtent: childExtent,
      // 通过等于页面上实际显示的长度。滚动到顶部后,开始变小,直到等于 0。手动设置的话,须小于 maxPaintExtent
      paintExtent: childExtent,
      // 注:布局高度要逐渐变小,否则下一个 sliver 滚到顶部会有停顿(可尝试注释掉这段代码)。
      layoutExtent: paintedChildSize,
      cacheExtent: cacheExtent,
      // 最大可绘制长度,常等于 childExtent。取值必须小于 constraints.remainingPaintExtent。
      maxPaintExtent: childExtent,
    );

    // 3.设置并保存 paintOffset 到 parentData 中,待稍后确定子节点位置(offset)
    // setChildParentData(child!, constraints, geometry!);

    final SliverPhysicalParentData childParentData =
        child!.parentData! as SliverPhysicalParentData;
    final topOffset =
        constraints.viewportMainAxisExtent - constraints.remainingPaintExtent;
    final offset = topOffset + (geometry?.paintOrigin ?? 0);
    if (offset < _pinnedOffset) {
      final paintOffset = _pinnedOffset - offset;
      childParentData.paintOffset = Offset(0.0, paintOffset);
    } else {
      childParentData.paintOffset = Offset(0.0, -constraints.scrollOffset);
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null && geometry!.visible) {
      final SliverPhysicalParentData childParentData =
          child!.parentData! as SliverPhysicalParentData;
      print("offset: $offset, paintOffset: ${childParentData.paintOffset}");
      // 4.绘制子节点
      // topOffset = constraints.viewportMainAxisExtent - constraints.remainingPaintExtent;
      //其中 offset = topOffset(sliver 到达顶部后等于 0) + paintOrigin(默认等于0)
      //其中 paintOffset(默认等于0)
      //即到达顶部后,如果两者都等于0,并且 visible 不等于 false,则 child 保持当前位置不变。
      context.paintChild(child!, offset + childParentData.paintOffset);
    }
  }
}

标签:paintOffset,pinnedOffset,offset,childExtent,固定,child,sliver,flutter,constraints
From: https://www.cnblogs.com/lemos/p/16602957.html

相关文章