近些日子在 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互換位置的組件