近些日子在 UIMovement 上看到了一个比较酷炫的登陆页效果,以下:git
以为很酷炫,就本身实现了一下,效果以下:github
下面就来一步一步的分析是如何作出来的。微信
首先仍是老套路,看一下都须要作什么事情:async
需求了解了,下面就是一步一步的实现效果。ide
在这里咱们须要注意的有一点:动画
在咱们使用 showModalBottomSheet
时,默认的背景是白色的,也就是说咱们本身设置的圆角是无论用的,ui
因此要给这个 BottomSheet
一个背景,这个参数在 showModalBottomSheet
方法中就有:this
showModalBottomSheet(
context: context,
backgroundColor: Colors.transparent,
builder: (context) {
Future.delayed(Duration(milliseconds: 50), () {
_animationController.forward();
});
return AnimatedUserAgreement(
animation: _animation,
);
});
)
复制代码
设置一个 backgroundColor
就ok了。lua
这里我是写了一个 「AnimatedWidget」,对 Dialog 里面的 Widget 同时执行透明度和位置的动画:spa
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 的话,会溢出。
有两种颜色这个需求仍是比较简单的,使用 「TextSpan」就搞定了。
代码我就不贴了。
重点来了,这个功能是相对来讲比较复杂的,可是只要咱们了解需求,写起来也是比较简单。
首先咱们也是把这个功能点拆分一下:
也仍是一步一步来。
该功能不用考虑太多,既然有点击手势,那必然会使用 GestureDetector
,
而后使用 GestureDetector
的 onTapDown
参数,该参数是在「点击按下」时回调:
onTapDown: (d) {
setState(() {
btnColor = btnColors[1];
});
复制代码
也没有多余复杂的东西,就是改变按钮的颜色。
如何处理点击后?或者没有点击?
GestureDetector
也帮咱们封装好了:
首先咱们处理取消点击:
onTapCancel: () {
setState(() {
btnColor = btnColors[0];
});
}
复制代码
把颜色变回去就好了。
而后处理抬起时的逻辑,在抬起时也有两个逻辑:
首先说一下第一点:
缩小成圆形的时候是有一个回弹效果的,因此不能使用 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,
),
)
],
),
);
},
),
复制代码
这个也很简单,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);
});
});
});
}
复制代码
这个相对来讲就更简单了,咱们只须要在 logo 的上方套一个 AnimatedContainer
,
而后监听 dialog 是否已经 dismiss,若是已经 dismiss 那么则调整 margin 的值就行了。
代码以下:
setState(() {
logoMargin = 100;
});
复制代码
这样正好 dialog 会有一个下移的动画,而 logo 上移,就达到了咱们想要的效果。
如何把文字呈波浪形弹出?
咱们最早想到的确定就是动画,由于也只有动画才有这种回弹的效果,
那这么多文字,每个都要设置动画?
答案是确定的。
既然知道了,那咱们也只能循序渐进的作了。
能够看到,每个文字都是由透明转为不透明,而且还会更改位置,
那咱们仍是先来封装一个 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,
),
);
}
}
复制代码
这里也有两点须要注意:
封装好之后咱们就能够愉快的玩耍了:
void startAnim() async {
for (int i = 0; i < strs.length; i++) {
Future.delayed(
Duration(
milliseconds: i * 100,
), () {
_strController[i].forward();
});
}
}
复制代码
文字弹出效果时间为 600ms,这里设置每隔100ms作一个动画,
这样的效果是比较好的,更像波浪形弹出。
主动弹出键盘咱们应该都有所了解,使用 FocusNode
,
这里咱们也是只须要判断最后一个动画什么时候作完,而后把隐藏的键盘弹出,而且把键盘弹出就ok了。
代码以下:
_strPositionAnimation[strs.length - 1].addStatusListener((status){
if(status == AnimationStatus.completed){
setState(() {
opacity = 1;
FocusScope.of(context).requestFocus(myFocusNode);
});
}
});
复制代码
实现这个页面耗费了我一个晚上的时间,不得不说,东西仍是很多的。
想要实现这样酷炫的登陆页,仍是比较复杂。
这里我实现的还不是很完美,看起来对比原图有些「着急」。
不过无所谓了,就是改变一下动画持续时间的事。
仍是那句话,梳理好需求,什么都好作。
代码已上传至 GitHub:github.com/wanglu1209/…
另我我的建立了一个「Flutter 交流群」,能够添加我我的微信 「17610912320」来入群。
推荐阅读:
Flutter | WReorderList 一个能够指定两个item互换位置的组件
Flutter | 如何实现一个水波纹扩散效果的 Widget