Flutter | 一个超级酷炫的登录页是怎样炼成的

近些日子在 UIMovement[1] 上看到了一个比较酷炫的登录页效果,如下:

640?wx_fmt=gif

觉得很酷炫,就自己实现了一下,效果如下:

640?wx_fmt=gif

下面就来一步一步的分析是如何做出来的。

需求分析

首先还是老套路,看一下都需要做什么事情:

1.首先我们最清晰明了的需求就是点击「注册」弹出 Dialog2.弹出 Dialog 后延迟一段时间弹出 Dialog 里的内容3.Dialog 内说明文字有两种颜色4.点击 「Accepter」按钮会变色缩小回弹并展示 ok图标5.点击「Accepter」按钮时 Dialog 内其他文字都被「白色遮罩」6.「Accepter」按钮 动画结束后 dismiss 掉当前dialog 并把 logo向上移7.跳转到第二页,文字呈波浪形弹出8.文字弹出后显示对话框并弹出键盘

开始实现

需求了解了,下面就是一步一步的实现效果。

1. 点击「注册」弹出 Dialog

在这里我们需要注意的有一点:

在我们使用 showModalBottomSheet 时,默认的背景是白色的,也就是说我们自己设置的圆角是不管用的,

所以要给这个 BottomSheet 一个背景,这个参数在 showModalBottomSheet 方法中就有:

showModalBottomSheet(	
  context: context,	
  backgroundColor: Colors.transparent,	
  builder: (context) {	
    Future.delayed(Duration(milliseconds: 50), () {	
      _animationController.forward();	
    });	
    return AnimatedUserAgreement(	
      animation: _animation,	
    );	
  });	
)

设置一个 backgroundColor 就ok了。

2. 弹出 Dialog 后延迟一段时间弹出 Dialog 里的内容

这里我是写了一个 「AnimatedWidget」,对 Dialog 里面的 Widget 同时执行透明度和位置的动画:

return Container(	
  height: 270,	
  padding: EdgeInsets.all(30),	
  decoration: BoxDecoration(	
    borderRadius: BorderRadius.circular(30), color: Colors.white),	
  child: Opacity(	
    opacity: _opacityTween.evaluate(animation),	
    child: Stack(	
      children: <Widget>[	
        SingleChildScrollView(	
          child: Container(	
            child: UserAgreementDialog(),	
            margin: EdgeInsets.only(top: _offsetTween.evaluate(animation)),	
          ),	
        )	
      ],	
    ),	
  ),	
);

可能细心的同学看出来上面的代码有一些问题:

为什么要加一个 SingleChildScrollView

因为我这里改变位置使用的动画效果是 「动态改变 Container margin 的值」

所以如果不使用 ScrollView 的话,会溢出。

3. Dialog 内说明文字有两种颜色

有两种颜色这个需求还是比较简单的,使用 「TextSpan」就搞定了。

代码我就不贴了。

4. 点击 「Accepter」按钮会变色缩小回弹并展示 ok图标

重点来了,这个功能是相对来说比较复杂的,但是只要我们了解需求,写起来也是比较简单。

首先我们也是把这个功能点拆分一下:

1.点击按钮的时候会变色2.点击后会变回原来的颜色并缩小成一个圆形3.变成圆形后动画效果展示 ok 图标

也还是一步一步来。

1. 点击按钮的时候会变色

该功能不用考虑太多,既然有点击手势,那必然会使用 GestureDetector

然后使用 GestureDetector 的 onTapDown 参数,该参数是在「点击按下」时回调:

onTapDown: (d) {	
  setState(() {	
    btnColor = btnColors[1];	
  });

也没有多余复杂的东西,就是改变按钮的颜色。

2. 点击后会变回原来的颜色并缩小成一个圆形

如何处理点击后?或者没有点击?

GestureDetector 也帮我们封装好了:

•onTapUp:在点击抬起时回调•onTapCancel:在取消点击时回调

首先我们处理取消点击:

onTapCancel: () {	
  setState(() {	
    btnColor = btnColors[0];	
  });	
}

把颜色变回去就行了。

然后处理抬起时的逻辑,在抬起时也有两个逻辑:

1.按钮会缩小成圆形2.缩小成圆形的时候会弹出 ok 图标

首先说一下第一点:

缩小成圆形的时候是有一个回弹效果的,所以不能使用 AnimatedContainer 这种,必须要使用 Animation 才有这种效果。

所以我使用了 AnimatedBuilder 来包装这个 Widget。

然后说一下第二点:

如何在缩小成圆形的时候弹出 ok 图标?

我们可以使用 IndexStack,在开始缩小动画的时候切换 index,因为 ok 图标开始时的缩放状态是 0,所以页面上是没有图标的,方便我们后续做动画。

Widget 代码如下:

AnimatedBuilder(	
  animation: _widthAnimation,	
  builder: (BuildContext context, Widget child) {	
    return Container(	
      width: _widthAnimation.value,	
      alignment: Alignment.center,	
      decoration: BoxDecoration(	
        borderRadius: BorderRadius.all(Radius.circular(40)),	
        color: btnColor),	
      margin: EdgeInsets.only(top: btnMargin),	
      height: btnHeight,	
      child: IndexedStack(	
        alignment: Alignment.center,	
        index: index,	
        children: <Widget>[	
          Text(	
            'Accepteer',	
            style: TextStyle(fontSize: 18),	
          ),	
          ScaleTransition(	
            scale: _scaleTween.animate(_animation),	
            child: Image.asset(	
              'images/ok.png',	
              width: 35,	
              height: 35,	
            ),	
          )	
        ],	
      ),	
    );	
  },	
),

5. 点击「Accepter」按钮时 Dialog 内其他文字都被「白色遮罩」

这个也很简单,Container 默认就有一个参数是:foregroundDecoration,我们只需要在这个参数里设置上我们想要遮罩的颜色就可以了。

但是也需要注意一点,如果最开始使用的遮罩颜色为「透明色」,那么会整体变黑一下,这个具体的原因我也没研究明白,有知道的大佬可以告知一下。

这样按钮点击后的效果就全部完成,代码如下:

onTapUp: (d) {	
  Future.delayed(Duration(milliseconds: 60), () {	
    setState(() {	
      foregroundColor = Colors.white70;	
      btnColor = btnColors[0];	
      index = 1;	
    });	
    _widthController.forward();	
    Future.delayed(Duration(milliseconds: 200), () {	
      _controller.forward().then((va) {	
        Navigator.pop(context);	
      });	
    });	
  });	
}

6. 动画结束后 dismiss 掉当前dialog 并把 logo向上移

这个相对来说就更简单了,我们只需要在 logo 的上方套一个 AnimatedContainer ,

然后监听 dialog 是否已经 dismiss,如果已经 dismiss 那么则调整 margin 的值就好了。

代码如下:

setState(() {	
  logoMargin = 100;	
});

这样正好 dialog 会有一个下移的动画,而 logo 上移,就达到了我们想要的效果。

7. 跳转到第二页,文字呈波浪形弹出

如何把文字呈波浪形弹出?

我们最先想到的肯定就是动画,因为也只有动画才有这种回弹的效果,

那这么多文字,每一个都要设置动画?

答案是肯定的。

既然知道了,那我们也只能按部就班的做了。

可以看到,每一个文字都是由透明转为不透明,并且还会更改位置,

那我们还是先来封装一个 AnimatedWidget

代码如下:

class AnimatedStrWidget extends AnimatedWidget {	
  final Tween<double> _opacityTween = Tween(begin: 0, end: 1);	
  final Tween<Offset> _offsetTween =	
      Tween(begin: Offset(0, 3), end: Offset(0, 0));	
  final Widget child;	

	
  AnimatedStrWidget(	
      {Key key, @required Animation<double> animation, @required this.child})	
      : super(key: key, listenable: animation);	

	
  @override	
  Widget build(BuildContext context) {	
    final Animation<double> animation = listenable;	
    return Opacity(	
      opacity: _opacityTween.evaluate(animation) < 0	
          ? 0	
          : _opacityTween.evaluate(animation) > 1	
              ? 1	
              : _opacityTween.evaluate(animation),	
      child: SlideTransition(	
        position: _offsetTween.animate(animation),	
        child: child,	
      ),	
    );	
  }	
}

这里也有两点需要注意:

1.透明度不能有负数并且不能大于1,因为我们这个效果是要有回弹的效果,所以要做判断。2.Tween<Offset> 这里的值是整个高度的倍数,所以不要以为是像素值。

封装好以后我们就可以愉快的玩耍了:

void startAnim() async {	
  for (int i = 0; i &lt; strs.length; i++) {	
    Future.delayed(	
      Duration(	
        milliseconds: i * 100,	
      ), () {	
        _strController[i].forward();	
      });	
  }	
}

文字弹出效果时间为 600ms,这里设置每隔100ms做一个动画,

这样的效果是比较好的,更像波浪形弹出。

8. 文字弹出后显示对话框并弹出键盘

主动弹出键盘我们应该都有所了解,使用 FocusNode

这里我们也是只需要判断最后一个动画何时做完,然后把隐藏的键盘弹出,并且把键盘弹出就ok了。

代码如下:

_strPositionAnimation[strs.length - 1].addStatusListener((status){	
  if(status == AnimationStatus.completed){	
    setState(() {	
      opacity = 1;	
      FocusScope.of(context).requestFocus(myFocusNode);	
    });	
  }	
});

总结

实现这个页面耗费了我一个晚上的时间,不得不说,东西还是不少的。

想要实现这样酷炫的登录页,还是比较复杂。

这里我实现的还不是很完美,看起来对比原图有些「着急」。

不过无所谓了,就是改变一下动画持续时间的事。

还是那句话,梳理好需求,什么都好做。

代码已上传至 GitHub:https://github.com/wanglu1209/WFlutterDemo

另我个人创建了一个「Flutter 交流群」,可以添加我个人微信 「17610912320」来入群。

推荐阅读:

Flutter | WReorderList 一个可以指定两个item互换位置的组件

Flutter | 如何实现一个水波纹扩散效果的 Widget

Flutter | 自定义一个 Stepper 步骤组件

640?wx_fmt=png

References

[1] UIMovement: https://uimovement.com/

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值