如何使用Flutter在同一屏幕路径上创建英雄风格的动画?

问题描述 投票:3回答:1

Mockup

我正在尝试创建上面的模型中描述的动态动画。我需要的是:

  • 圆形,代表化身(例如用户图片);
  • 一个位于其下方的文本。
  • 低于那些(使用屏幕的一半)是可滚动的PageView

动画应如下所示:

开始:在Stack中都以开头为中心。 动画:缩小和滑动必须位于头像右侧的文本(具有可变长度)。 结束:作为样机中的第二个图像。并排,而下面的内容保持滚动。

想在SliverPersistentHeader结合CustomMultiChildLayout,但问题是文本开始居中并且结束左对齐,我可以动态地动画这个。我试图在最后删除居中文本的偏移,但感觉不对。

任何帮助或只有这个动画的样本将不胜感激。谢谢。

animation dart flutter flutter-layout
1个回答
6
投票

您将需要一个Sliver根据滚动偏移量为您的布局设置动画。更具体地说,SliverPersistentHeader在你的情况下。

但是CustomMultiChildLayout不是必需的,你可以使用tweens和align / padding / stuff来获得相同的结果。但是如果您的布局开始变得过于复杂,您可以试一试。

诀窍是使用SliverPersistentHeader给出的滚动偏移量来计算当前进度。然后使用该进展将元素定位在其原始位置和最终位置之间。

这是一个原始的例子:

enter image description here

class TransitionAppBar extends StatelessWidget {
  final Widget avatar;
  final Widget title;

  const TransitionAppBar({this.avatar, this.title, Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SliverPersistentHeader(
      pinned: true,
      delegate: _TransitionAppBarDelegate(
        avatar: avatar,
        title: title,
      ),
    );
  }
}

class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {
  final _avatarTween =
      SizeTween(begin: Size(150.0, 150.0), end: Size(50.0, 50.0));
  final _avatarMarginTween =
      EdgeInsetsTween(begin: EdgeInsets.zero, end: EdgeInsets.only(left: 10.0));
  final _avatarAlignTween =
      AlignmentTween(begin: Alignment.topCenter, end: Alignment.centerLeft);

  final _titleMarginTween = EdgeInsetsTween(
      begin: EdgeInsets.only(top: 150.0 + 5.0),
      end: EdgeInsets.only(left: 10.0 + 50.0 + 5.0));
  final _titleAlignTween =
      AlignmentTween(begin: Alignment.center, end: Alignment.centerLeft);

  final Widget avatar;
  final Widget title;

  _TransitionAppBarDelegate({this.avatar, this.title})
      : assert(avatar != null),
        assert(title != null);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    final progress = shrinkOffset / 200.0;

    final avatarSize = _avatarTween.lerp(progress);
    final avatarMargin = _avatarMarginTween.lerp(progress);
    final avatarAlign = _avatarAlignTween.lerp(progress);

    final titleMargin = _titleMarginTween.lerp(progress);
    final titleAlign = _titleAlignTween.lerp(progress);
    return Stack(
      fit: StackFit.expand,
      children: <Widget>[
        Padding(
          padding: avatarMargin,
          child: Align(
            alignment: avatarAlign,
            child: SizedBox.fromSize(size: avatarSize, child: avatar),
          ),
        ),
        Padding(
          padding: titleMargin,
          child: Align(
            alignment: titleAlign,
            child: DefaultTextStyle(
                style: Theme.of(context).textTheme.title, child: title),
          ),
        )
      ],
    );
  }

  @override
  double get maxExtent => 200.0;

  @override
  double get minExtent => 100.0;

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

您可以使用CustomScrollView:

Scaffold(
  body: CustomScrollView(
    slivers: <Widget>[
      TransitionAppBar(
        avatar: Material(
          color: Colors.blue,
          elevation: 3.0,
        ),
        title: Text("Hello World"),
      ),
      SliverList(
        delegate: SliverChildBuilderDelegate((context, index) {
          return ListTile(
            title: Text('$index'),
          );
        }),
      )
    ],
  ),
);
© www.soinside.com 2019 - 2024. All rights reserved.