如何让 SliverPersistentHeader “过度生长"? [英] How to get the SliverPersistentHeader to "overgrow"

查看:14
本文介绍了如何让 SliverPersistentHeader “过度生长"?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的 CustomScrollView 中使用了一个 SliverPersistentHeader 来拥有一个在用户滚动时会收缩和增长的持久标头,但是当它达到其最大尺寸时,它会感觉有点僵硬,因为它不会过度生长".

I'm using a SliverPersistentHeader in my CustomScrollView to have a persistent header that shrinks and grows when the user scrolls, but when it reaches its maximum size it feels a bit stiff since it doesn't "overgrow".

这是我想要的行为的视频(来自 Spotify 应用)和我的行为:

Here is a video of the behaviour I want (from the Spotify app) and the behaviour I have:

.

推荐答案

在寻找解决这个问题的方法时,我遇到了三种不同的解决方法:

While looking for a solution for this problem, I came across three different ways to solve it:

  1. 创建一个 Stack,其中包含 CustomScrollView 和一个标题小部件(覆盖在滚动视图的顶部),提供一个 ScrollControllerCustomScrollView 并将控制器传递给 header 小部件以调整其大小
  2. 使用一个ScrollController,将其传递给CustomScrollView,并使用控制器的值来调整SliverPersistentHeader的maxExtent(这是 Eugene 推荐的).
  3. 编写我自己的 Sliver 来做我想做的事.
  1. Create a Stack that contains the CustomScrollView and a header widget (overlaid on top of the scroll view), provide a ScrollController to the CustomScrollView and pass the controller to the header widget to adjust its size
  2. Use a ScrollController, pass it to the CustomScrollView and use the value of the controller to adjust the maxExtent of the SliverPersistentHeader (this is what Eugene recommended).
  3. Write my own Sliver to do exactly what I want.

我遇到了解决方案 1 &2:

I ran into problems with solution 1 & 2:

  1. 这个解决方案对我来说似乎有点骇人听闻".我也遇到了问题,拖动"标题不再滚动,因为标题不再 inside CustomScrollView.
  2. 在滚动过程中调整条子的大小会导致奇怪的副作用.值得注意的是,在滚动过程中,标题和下面的条子之间的距离会增加.

这就是我选择解决方案 3 的原因.我确信我实现它的方式不是最好的,但它完全符合我的要求:

That's why I opted for solution 3. I'm sure the way I implemented it, is not the best, but it works exactly as I want:

import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'dart:math' as math;

/// The delegate that is provided to [ElSliverPersistentHeader].
abstract class ElSliverPersistentHeaderDelegate {
  double get maxExtent;
  double get minExtent;

  /// This acts exactly like `SliverPersistentHeaderDelegate.build()` but with
  /// the difference that `shrinkOffset` might be negative, in which case,
  /// this widget exceeds `maxExtent`.
  Widget build(BuildContext context, double shrinkOffset);
}

/// Pretty much the same as `SliverPersistentHeader` but when the user
/// continues to drag down, the header grows in size, exceeding `maxExtent`.
class ElSliverPersistentHeader extends SingleChildRenderObjectWidget {
  final ElSliverPersistentHeaderDelegate delegate;
  ElSliverPersistentHeader({
    Key key,
    ElSliverPersistentHeaderDelegate delegate,
  })  : this.delegate = delegate,
        super(
            key: key,
            child:
                _ElSliverPersistentHeaderDelegateWrapper(delegate: delegate));

  @override
  _ElPersistentHeaderRenderSliver createRenderObject(BuildContext context) {
    return _ElPersistentHeaderRenderSliver(
        delegate.maxExtent, delegate.minExtent);
  }
}

class _ElSliverPersistentHeaderDelegateWrapper extends StatelessWidget {
  final ElSliverPersistentHeaderDelegate delegate;

  _ElSliverPersistentHeaderDelegateWrapper({Key key, this.delegate})
      : super(key: key);

  @override
  Widget build(BuildContext context) =>
      LayoutBuilder(builder: (context, constraints) {
        final height = constraints.maxHeight;
        return delegate.build(context, delegate.maxExtent - height);
      });
}

class _ElPersistentHeaderRenderSliver extends RenderSliver
    with RenderObjectWithChildMixin<RenderBox> {
  final double maxExtent;
  final double minExtent;

  _ElPersistentHeaderRenderSliver(this.maxExtent, this.minExtent);

  @override
  bool hitTestChildren(HitTestResult result,
      {@required double mainAxisPosition, @required double crossAxisPosition}) {
    if (child != null) {
      return child.hitTest(result,
          position: Offset(crossAxisPosition, mainAxisPosition));
    }
    return false;
  }

  @override
  void performLayout() {
    /// The amount of scroll that extends the theoretical limit.
    /// I.e.: when the user drags down the list, although it already hit the
    /// top.
    ///
    /// This seems to be a bit of a hack, but I haven't found a way to get this
    /// information in another way.
    final overScroll =
        constraints.viewportMainAxisExtent - constraints.remainingPaintExtent;

    /// The actual Size of the widget is the [maxExtent] minus the amount the
    /// user scrolled, but capped at the [minExtent] (we don't want the widget
    /// to become smaller than that).
    /// Additionally, we add the [overScroll] here, since if there *is*
    /// "over scroll", we want the widget to grow in size and exceed
    /// [maxExtent].
    final actualSize =
        math.max(maxExtent - constraints.scrollOffset + overScroll, minExtent);

    /// Now layout the child with the [actualSize] as `maxExtent`.
    child.layout(constraints.asBoxConstraints(maxExtent: actualSize));

    /// We "clip" the `paintExtent` to the `maxExtent`, otherwise the list
    /// below stops moving when reaching the border.
    ///
    /// Tbh, I'm not entirely sure why that is.
    final paintExtent = math.min(actualSize, maxExtent);

    /// For the layout to work properly (i.e.: the following slivers to
    /// scroll behind this sliver), the `layoutExtent` must not be capped
    /// at [minExtent], otherwise the next sliver will "stop" scrolling when
    /// [minExtent] is reached,
    final layoutExtent = math.max(maxExtent - constraints.scrollOffset, 0.0);

    geometry = SliverGeometry(
      scrollExtent: maxExtent,
      paintExtent: paintExtent,
      layoutExtent: layoutExtent,
      maxPaintExtent: maxExtent,
    );
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (child != null) {
      /// This sliver is always displayed at the top.
      context.paintChild(child, Offset(0.0, 0.0));
    }
  }
}

这篇关于如何让 SliverPersistentHeader “过度生长"?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆