手记

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

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

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

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

需求分析

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

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 < 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);

    });

  }

});


总结

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

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

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

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

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


References

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



0人推荐
随时随地看视频
慕课网APP