代码之家  ›  专栏  ›  技术社区  ›  Miguel Ruivo

如何用颤振在同一屏幕上创建英雄风格的动画?

  •  3
  • Miguel Ruivo  · 技术社区  · 8 年前

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

    • 表示虚拟人物的圆形(例如用户图片);
    • 以文本为中心的文本。
    • 在那些(使用屏幕的一半)下面是一个可滚动的页面视图。

    动画应如下所示:

    begin: in a stack both centraled at the begin.
    动画: 缩放和滑动文本(with variable length )that must be to the right of the avatar.
    结束: as the second image in the mockup.当下面的内容不断滚动时,左右移动。

    在 sliverPersistentHeader combined with customMultichildLayout 中考虑,但问题是文本开始居中,结束对齐到左侧,我可以动态地设置此动画。我试图在结尾处删除居中文本的偏移量,但感觉不对。

    任何帮助或仅此动画的示例将不胜感激。谢谢您。

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

    • 表示虚拟人物的圆形(例如用户图片);
    • 位于其下方居中的文本。
    • 在那些(使用一半屏幕的)下面是一个可滚动的 PageView .

    动画应如下所示:

    开始: 在一个 Stack 两者都集中在开头。
    动画: 缩小和滑动文本(使用 可变长度 )那一定是在阿凡达的右边。
    结束:
    作为模型中的第二个图像。当下面的内容不断滚动时,请将其并排放置。

    思想在 SliverPersistentHeader 结合 CustomMultiChildLayout 但问题是,文本从中心开始,到左对齐,我可以动态地设置动画。我试图在结尾处删除居中文本的偏移量,但感觉不对。

    任何帮助或仅此动画的示例将不胜感激。谢谢您。

    1 回复  |  直到 7 年前
        1
  •  5
  •   Rémi Rousselet    8 年前

    您需要一个小条来根据滚动偏移设置布局动画。更具体地说,在您的情况下, sliverPersistentHeader

    custommultichildlayout 是不必要的,但是,您可以使用tweens和align/padding/stufactures获得相同的结果。但如果你的布局变得过于复杂,你可以尝试一下。

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

    以下是一个原始示例:

    class transitionAppbar extends statelesswidget{
    最终的小部件虚拟人物;
    最终小部件标题;
    
    const transitionAppBar(this.avatar,this.title,key key):super(key:key);
    
    @重写
    小部件构建(BuildContext上下文){
    返回sliverPersistentHeader(
    钉住:真的,
    学员:_TransitionAppBardelegate(
    化身:化身,
    题目:标题,
    )
    ;
    }
    }
    
    类TransitionAppBardelegate扩展SliverPersistentHeaderDelegate{
    最后一次=
    大小介于(开始:大小(150.0,150.0),结束:大小(50.0,50.0));
    最后一部《阿凡达边缘之旅》=
    edgeinsettween(begin:edgeinsets.zero,end:edgeinsets.only(left:10.0));
    最后一部影片=
    对齐内部(开始:Alignment.TopCenter,结束:Alignment.CenterLeft);
    
    final titlemargintween=边缘设置tween(
    开始:边缘集。仅限(顶部:150.0+5.0)
    结束:边缘集。仅(左:10.0+50.0+5.0));
    最后一个=
    AlignmentTween(开始:Alignment.Center,结束:Alignment.CenterLeft);
    
    最终的小部件虚拟人物;
    最终小部件标题;
    
    _ transitionAppBardelegate(this.avatar,this.title)
    :断言(化身!= NULL)
    断言(标题)!= NULL);
    
    @重写
    小部件构建
    buildContext上下文,Double ShrinkOffset,bool OverlapsContent){
    最终进度=收缩偏移/200.0;
    
    final avatarsize=_avatartween.lerp(进度);
    final avatarmargin=_avatarmargintween.lerp(进度);
    final avatarAlign=_avatarAlignTween.lerp(进度);
    
    final titlemargin=\u titlemargintween.lerp(进度);
    final titlealign=\u titlealigntheen.lerp(进度);
    返回堆栈(返回堆栈)
    配合:stackfit.expand,
    子项:<widget>。[
    垫料
    填充:AvatarMargin,
    儿童:对齐
    对齐:AvatarAlign,
    child:sizedbox.fromsize(大小:avatar size,子:avatar)
    )
    )
    垫料
    填充物:奶精,
    儿童:对齐
    对齐方式:标题对齐,
    子级:默认文本样式(
    style:theme.of(context).textTheme.title,child:title),
    )
    )
    ,
    ;
    }
    
    @重写
    double get maxextent=>200.0;
    
    @重写
    double get minextent=>100.0;
    
    @重写
    bool shouldrebuild(_transitionAppBardelegate oldDelegate){
    返回化身!=olddelegate.avatar标题!=olddelegate.title;
    }
    }
    < /代码> 
    
    

    可用于自定义滚动视图:

    脚手架( 正文:自定义滚动视图( 长条:<小工具>。[ 转换Appbar( 化身:材质( 颜色:颜色。蓝色, 海拔高度:3, ) 标题:文本(“Hello World”), ) SliverList 委托:SliverChildBuilderDelegate((上下文,索引){ 返回ListTile( 标题:文本(“$index”), ; } ) , ) ; < /代码> 在你的情况下。

    CustomMultiChildLayout但这不是必需的,您可以使用镊子和对齐/填充/填充物来获得相同的结果。但如果你的布局变得过于复杂,你可以试试看。

    技巧是使用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;
      }
    }
    

    可用于自定义滚动视图:

    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'),
              );
            }),
          )
        ],
      ),
    );