使用 SliverAppBar 设计这个动画

Posted

技术标签:

【中文标题】使用 SliverAppBar 设计这个动画【英文标题】:Design this animation using SliverAppBar flutter 【发布时间】:2020-04-06 20:12:31 【问题描述】:

Here's what I want to build 但我能够做到这一点

通过以下代码,

@override
  Widget build(BuildContext context) 
    return Scaffold(
      body: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled)
          return [
            SliverAppBar(
              expandedHeight: 120,
              floating: false,
              pinned: false,
              flexibleSpace: Container(
                padding: EdgeInsets.all(10),
                height: 160,
                width: double.infinity,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.end,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Align(
                        alignment: Alignment.topCenter,
                        child: Text(
                          'Hello World',
                        )),
                    Padding(
                      padding: EdgeInsets.only(top: 16),
                      child: Image.asset(
                        'assets/images/banner.png',
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ];
        ,
        body: ListView.builder(),
      ),
    );
  

我曾尝试使用灵活扩展的 SliverAppBar,但无法实现。


更新 - 列表的第一个元素在文本字段后面。我只想在动画完成时滚动

【问题讨论】:

就个人而言,我认为仅使用 SliversAppBar 是不可能的。您需要创建自己的自定义应用栏,然后使用 Hero Animation(将文本从左侧移动到中心)、AnimatedContainer(降低容器高度)、AnimationController、FadeTransition,并监听您的 Listview 控制器。有点高级。 让我尝试一下,实施后会回复您。谢谢@FadhliS 我将尝试使用我提到的所有小部件为您提供解决方案。我也不容易达到你想要的效果。您可以阅读我发现的这篇博客文章,以了解动画的工作原理,并希望对您有所帮助。 smashingmagazine.com/2019/10/animation-apps-flutter 【参考方案1】:

更新的答案,

   @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          TransitionAppBar(
            extent: 250,
            avatar: Text("Rancho"),
            title: Container(
              margin: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0),
              decoration: BoxDecoration(
                  color: Colors.grey[200],
                  borderRadius: BorderRadius.all(Radius.circular(5.0))),
              child: Row(children: <Widget>[
                Padding(
                  padding: EdgeInsets.only(left: 20.0, right: 10.0),
                  child: Icon(Icons.search),
                ),
                Expanded(
                  child: TextFormField(
                    keyboardType: TextInputType.text,
                    textInputAction: TextInputAction.done,
                    cursorColor: Colors.black,
                    autofocus: false,
                    style: TextField_Style,
                    decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.transparent,
                        contentPadding:
                            EdgeInsets.symmetric(vertical: 10, horizontal: 15),
                        hintText: "Search",
                        border: InputBorder.none,
                        disabledBorder: OutlineInputBorder(
                          borderSide: new BorderSide(color: Colors.transparent),
                          borderRadius: new BorderRadius.circular(2),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderSide: new BorderSide(color: Colors.transparent),
                          borderRadius: new BorderRadius.circular(2),
                        )),
                  ),
                )
              ]),
            ),
          ),
          SliverList(
              delegate: SliverChildBuilderDelegate((context, index) 
            return Container(
                color: Colors.blue,
                child: ListTile(
                  title: Text("$indexa"),
                ));
          , childCount: 25))
        ],
      ),
    );
   

.

class TransitionAppBar extends StatelessWidget 
  final Widget avatar;
  final Widget title;
  final double extent;

  TransitionAppBar(this.avatar, this.title, this.extent = 250, Key key) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return SliverPersistentHeader(
      pinned: true,
      delegate: _TransitionAppBarDelegate(
        avatar: avatar,
        title: title,
        extent: extent > 200 ? extent : 200
      ),
    );
  


class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate 
  final _avatarMarginTween = EdgeInsetsTween(
      begin: EdgeInsets.only(bottom: 70, left: 30),
      end: EdgeInsets.only(left: 0.0, top: 30.0));
  final _avatarAlignTween =
      AlignmentTween(begin: Alignment.bottomLeft, end: Alignment.topCenter);

  final Widget avatar;
  final Widget title;
  final double extent;

  _TransitionAppBarDelegate(this.avatar, this.title, this.extent = 250)
      : assert(avatar != null),
        assert(extent == null || extent >= 200),
        assert(title != null);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) 
    double tempVal = 34 * maxExtent / 100;
    final progress =  shrinkOffset > tempVal ? 1.0 : shrinkOffset / tempVal;
    print("Objechjkf === $progress $shrinkOffset");
    final avatarMargin = _avatarMarginTween.lerp(progress);
    final avatarAlign = _avatarAlignTween.lerp(progress);

    return Stack(
      children: <Widget>[
        AnimatedContainer(
          duration: Duration(milliseconds: 100),
          height: shrinkOffset * 2,
          constraints: BoxConstraints(maxHeight: minExtent),
          color: Colors.redAccent,
        ),
        Padding(
          padding: avatarMargin,
          child: Align(
            alignment: avatarAlign,
              child: avatar
          ),
        ),
        Padding(
          padding: EdgeInsets.only(bottom: 10),
          child: Align(
            alignment: Alignment.bottomCenter,
            child: title,
          ),
        )
      ],
    );
  

  @override
  double get maxExtent => extent;

  @override
  double get minExtent => (maxExtent * 68) / 100;

  @override
  bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) 
    return avatar != oldDelegate.avatar || title != oldDelegate.title;
  
 

【讨论】:

元素类型“_TransitionAppBarDelegate”不能分配给列表类型“Widget”。插入 headerSliv​​erBuilder 时出现此错误 一切正常,但如果你看到,列表的第一个元素在动画完成之前在文本字段后面。我只想在动画完成后滚动列表。 这只是演示代码,请根据需要自定义代码。 我管理了所有内容,但是如何避免列表的第一个元素在动画完成之前落后于 textformfield,因为动画取决于进度。请帮忙。 @nardeepsinh-vaghela 非常好,这正是我要找的东西啊哈太棒了,谢谢!

以上是关于使用 SliverAppBar 设计这个动画的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 NestedScrollView 滚动以在 SliverAppBar 上使用堆栈圆形头像?

36个优秀的APP界面动画设计(GIF动画欣赏)

如何使用 tabBar 实现 sliverAppBar

带有标签的Flutter SliverAppBar覆盖内容

动效设计Principle:动画介绍

APP设计中的交互动画是怎么实现的?