如何使SliverPersistentHeader“过度增长”

时间:2019-05-06 12:31:32

标签: flutter flutter-layout flutter-sliver

我在SliverPersistentHeader中使用CustomScrollView来拥有一个持久的标头,该标头在用户滚动时会缩小和增长,但是当达到最大大小时,它会变得有点僵硬,因为它不会t“过度增长”。

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

Video of behaviour

5 个答案:

答案 0 :(得分:3)

现在您可以创建自己的SliverPersistentHeaderDelegate并覆盖此参数”

@override
  OverScrollHeaderStretchConfiguration get stretchConfiguration =>
      OverScrollHeaderStretchConfiguration();

默认情况下,如果为null,则一旦添加,便可以拉伸视图。

这是我使用的课程:


class CustomSliverDelegate extends SliverPersistentHeaderDelegate {
  final Widget child;
  final Widget title;
  final Widget background;
  final double topSafeArea;
  final double maxExtent;

  CustomSliverDelegate({
    this.title,
    this.child,
    this.maxExtent = 350,
    this.background,
    this.topSafeArea = 0,
  });

  @override
  Widget build(BuildContext context, double shrinkOffset,
      bool overlapsContent) {
    final appBarSize = maxExtent - shrinkOffset;
    final proportion = 2 - (maxExtent / appBarSize);
    final percent = proportion < 0 || proportion > 1 ? 0.0 : proportion;
    return Theme(
      data: ThemeData.dark(),
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: maxExtent),
        child: Stack(
          children: [
            Positioned(
              bottom: 0.0,
              left: 0.0,
              right: 0.0,
              top: 0,
              child: background,
            ),
            Positioned(
              bottom: 0.0,
              left: 0.0,
              right: 0.0,
              child: Opacity(opacity: percent, child: child),
            ),
            Positioned(
              top: 0.0,
              left: 0.0,
              right: 0.0,
              child: AppBar(
                title: Opacity(opacity: 1 - percent, child: title),
                backgroundColor: Colors.transparent,
                elevation: 0,
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  OverScrollHeaderStretchConfiguration get stretchConfiguration =>
      OverScrollHeaderStretchConfiguration();

  @override
  double get minExtent => kToolbarHeight + topSafeArea;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }
}

答案 1 :(得分:1)

作为一种选择,您只需复制粘贴此代码即可查看其工作原理

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Page(),
      ),
    );
  }
}

class Page extends StatefulWidget {
  @override
  _PageState createState() => _PageState();
}

class _PageState extends State<Page> {
  static final double _initialToolbarHeight = 300;
  static final double _maxSizeFactor = 1.3; // image max size will 130%
  static final double _transformSpeed = 0.001; // 0.1 very fast,   0.001 slow

  ScrollController _controller;
  double _factor = 1;
  double _expandedToolbarHeight = _initialToolbarHeight;

  @override
  void initState() {
    _controller = ScrollController();
    _controller.addListener(_scrollListener);
    super.initState();
  }

  _scrollListener() {
    if (_controller.offset < 0) {
      _factor = 1 + _controller.offset.abs() * _transformSpeed;
      _factor = _factor.clamp(1, _maxSizeFactor);
      _expandedToolbarHeight = _initialToolbarHeight + _controller.offset.abs(); //
    } else {
      _factor = 1;
      _expandedToolbarHeight = _initialToolbarHeight; //
    }
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      physics: const BouncingScrollPhysics(),
      controller: _controller,
      slivers: [
        SliverPersistentHeader(
          floating: false,
          pinned: true,
          delegate: _SliverAppBarDelegate(
            minHeight: 300,
            maxHeight: 300,
            child: Transform.scale(
              scale: _factor,
              child: Image.network('https://picsum.photos/id/1025/990/660', fit: BoxFit.cover),
            ),
          ),
        ),
        SliverList(
          delegate: SliverChildListDelegate(getItems()),
        )
      ],
    );
  }

  getItems() {
    return List.generate(50, (pos) {
      return Container(
        height: 64,
        child: Text('item $pos'),
      );
    });
  }
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate({
    @required this.minHeight,
    @required this.maxHeight,
    @required this.child,
  });

  final double minHeight;
  final double maxHeight;
  final Widget child;

  @override
  double get minExtent => minHeight;

  @override
  double get maxExtent => maxHeight;

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new SizedBox.expand(child: child);
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return maxHeight != oldDelegate.maxHeight || minHeight != oldDelegate.minHeight || child != oldDelegate.child;
  }
}

Video of behaviour

答案 2 :(得分:1)

在寻找解决方案时,我遇到了三种不同的解决方法:

  1. 创建一个包含Stack和标题小部件(覆盖在滚动视图顶部)的CustomScrollView,向ScrollController提供一个CustomScrollView并传递控制器到标题小部件以调整其大小
  2. 使用ScrollController,将其传递到CustomScrollView,然后使用控制器的值来调整maxExtent的{​​{1}}(这就是{{3} })。
  3. 写我自己的Sliver来做我想要的事情。

我在解决方案1和2上遇到了问题

  1. 这个解决方案对我来说似乎有点“骇人听闻”。我还遇到了一个问题,即“拖动”页眉不再滚动,因为页眉不再内部SliverPersistentHeader
  2. 在滚动过程中调整条子的大小会产生奇怪的副作用。值得注意的是,在滚动过程中,页眉和下方条子之间的距离会增加。

这就是为什么我选择解决方案3的原因。我确定实现它的方式并不是最好的,但是它完全可以按照我的意愿工作:

CustomScrollView

答案 3 :(得分:1)

我通过简单地创建自定义SliverPersistentHeaderDelegate解决了这个问题。

只需覆盖StretchConfiguration的吸气剂即可。如果有用的话,这是我的代码。

class LargeCustomHeader extends SliverPersistentHeaderDelegate {
  LargeCustomHeader(
      {this.children,
      this.title = '',
      this.childrenHeight = 0,
      this.backgroundImage,
      this.titleHeight = 44,
      this.titleMaxLines = 1,
      this.titleTextStyle = const TextStyle(
          fontSize: 30,
          letterSpacing: 0.5,
          fontWeight: FontWeight.bold,
          height: 1.2,
          color: ColorConfig.primaryContrastColor)}) {}

  final List<Widget> children;
  final String title;
  final double childrenHeight;

  final String backgroundImage;

  final int _fadeDuration = 250;
  final double titleHeight;
  final int titleMaxLines;

  final double _navBarHeight = 56;

  final TextStyle titleTextStyle;

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return Container(
        constraints: BoxConstraints.expand(),
        decoration: BoxDecoration(
          // borderRadius: BorderRadius.vertical(bottom: Radius.circular(35.0)),
          color: Colors.black,
        ),
        child: Stack(
          fit: StackFit.loose,
          children: <Widget>[
            if (this.backgroundImage != null) ...[
              Positioned(
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                child: FadeInImage.assetNetwork(
                  placeholder: "assets/images/image-placeholder.png",
                  image: backgroundImage,
                  placeholderScale: 1,
                  fit: BoxFit.cover,
                  alignment: Alignment.center,
                  imageScale: 0.1,
                  fadeInDuration: const Duration(milliseconds: 500),
                  fadeOutDuration: const Duration(milliseconds: 200),
                ),
              ),
              Positioned(
                top: 0,
                left: 0,
                right: 0,
                bottom: 0,
                child: Container(
                  color: Color.fromRGBO(0, 0, 0, 0.6),
                ),
              ),
            ],
            Positioned(
                bottom: 0,
                left: 0,
                right: 0,
                top: _navBarHeight + titleHeight,
                child: AnimatedOpacity(
                    opacity: (shrinkOffset >= childrenHeight / 3) ? 0 : 1,
                    duration: Duration(milliseconds: _fadeDuration),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.stretch,
                      children: <Widget>[if (children != null) ...children],
                    ))),
            Positioned(
              top: _navBarHeight,
              left: 0,
              right: 0,
              height: titleHeight,
              child: Padding(
                padding: const EdgeInsets.only(
                    right: 30, bottom: 0, left: 30, top: 5),
                child: AnimatedOpacity(
                  opacity: (shrinkOffset >= childrenHeight + (titleHeight / 3))
                      ? 0
                      : 1,
                  duration: Duration(milliseconds: _fadeDuration),
                  child: Text(
                    title,
                    style: titleTextStyle,
                    maxLines: titleMaxLines,
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
              ),
            ),
            Container(
              color: Colors.transparent,
              height: _navBarHeight,
              child: AppBar(
                  elevation: 0.0,
                  backgroundColor: Colors.transparent,
                  title: AnimatedOpacity(
                    opacity:
                        (shrinkOffset >= childrenHeight + (titleHeight / 3))
                            ? 1
                            : 0,
                    duration: Duration(milliseconds: _fadeDuration),
                    child: Text(
                      title,
                    ),
                  )),
            )
          ],
        ));
  }

  @override
  double get maxExtent => _navBarHeight + titleHeight + childrenHeight;

  @override
  double get minExtent => _navBarHeight;

  // @override
  // FloatingHeaderSnapConfiguration get snapConfiguration => FloatingHeaderSnapConfiguration() ;

  @override
  OverScrollHeaderStretchConfiguration get stretchConfiguration =>
      OverScrollHeaderStretchConfiguration(
        stretchTriggerOffset: maxExtent,
        onStretchTrigger: () {},
      );

  double get maxShrinkOffset => maxExtent - minExtent;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    //TODO: implement specific rebuild checks
    return true;
  }
}

答案 4 :(得分:0)

您可以尝试将SliverAppBarstretch:true一起使用,并将要显示在应用栏中的小部件作为flexibleSpace传递。

这是一个例子

CustomScrollView(
  physics: BouncingScrollPhysics(),
  slivers: <Widget>[
    SliverAppBar(
      stretch: true,
      floating: true,
      backgroundColor: Colors.black,
      expandedHeight: 300,
      centerTitle: true,
      title: Text("My Custom Bar"),
      leading: IconButton(
        onPressed: () {},
        icon: Icon(Icons.menu),
      ),
      actions: <Widget>[
        IconButton(
          onPressed: () {},
          icon: Icon(Icons.search),
        )
      ],
      flexibleSpace: FlexibleSpaceBar(
        collapseMode: CollapseMode.pin,
        stretchModes: 
        [
          StretchMode.zoomBackground,
          StretchMode.blurBackground
        ],
        background: YourCustomWidget(),
      ),
    ),
    SliverList(
      delegate: SliverChildListDelegate(
        [
          Container(color: Colors.red, height: 300.0),
          Container(color: Colors.blue, height: 300.0),
        ],
      ),
    ),
  ],
);