在上一篇的時候虑椎,我們講解了怎么做一個登錄界面,但是之后呢俱笛?完全是草草結(jié)尾的感覺嘛捆姜,這不,接下來就是給大家詳細說說迎膜,這個登錄里面不得鳥的故事泥技。先來看一個登錄的過程~~
分析
可能上面的gif
圖不是很真切,這上面展示了兩個功能:
- 顏色變換的閃屏頁面
- 動畫效果的登錄頁面
有沒有感覺這樣的登錄好像還不錯呢磕仅,哈哈哈珊豹,接下來就詳細分析一下這其中的玄機~~
路由
一般我們的頁面跳轉(zhuǎn)都會涉及到路由,路由就是從一個頁面跳轉(zhuǎn)到另一個頁面的過程榕订,就比如Android中的Activity
或IOS中的ViewController
的跳轉(zhuǎn)店茶。
在Flutter中所以的路由都使用Navigator
來進行管理的,換句話說它就是讓這些本來相對獨立的個體形成一個完美的整體劫恒。那么Navigator
是直接管理的就是頁面嗎贩幻?當(dāng)然不是,實際上它管理是Route
對象两嘴,而且提供了管理堆棧的相關(guān)方法丛楚,比如:
- Navigator.push (入棧)
- Navigator.pop (出棧)
雖然能夠直接創(chuàng)建一個navigator
,但是呢憔辫,一般不建議這樣直接使用鸯檬,我們常常通過WidgetsApp
或者MaterialApp
去創(chuàng)建。還記得第一篇的時候螺垢,就跟大家提過喧务,F(xiàn)lutter提供了許多widgets,可幫助您構(gòu)建遵循Material Design
的應(yīng)用程序枉圃。Material應(yīng)用程序以MaterialApp widget開始功茴, 該widget在應(yīng)用程序的根部創(chuàng)建了一些有用的widget,其中包括一個Navigator
孽亲, 它管理由字符串標(biāo)識的Widget棧(即頁面路由棧)坎穿。Navigator可以讓您的應(yīng)用程序在頁面之間的平滑的過渡。 所以我們的應(yīng)用啟動一般這樣寫:
void main() {
runApp(MaterialApp(home: MyAppHome()));
}
那么返劲,home
所指向的頁面也就是我們棧中最底層的路由玲昧,那MaterialApp
到底是怎么創(chuàng)建這個底層路由的呢?它遵循以下幾個原則:
const MaterialApp({
Key key,
this.navigatorKey,
this.home,
this.routes = const <String, WidgetBuilder>{},
this.initialRoute,
this.onGenerateRoute,
this.onUnknownRoute,
//省略無關(guān)代碼
...
})
- 首先使用我們的
home
所指向的 - 如果失敗篮绿,那么就會使用
routes
路由表 - 如果路由表為空孵延,那么就會調(diào)用
onGenerateRoute
- 如果以上所有都失敗了,那么
onUnknownRoute
將會被調(diào)用
所以說如果要創(chuàng)建Navigator
亲配,那么以上四個必須有一個被使用尘应。
MaterialPageRoute
一般我們可以使用MaterialPageRoute
去進行路由:
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('My Page')),
body: Center(
child: FlatButton(
child: Text('POP'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
},
));
這種方式的就很明顯了,它是使用一種build
的方式去入棧(或者出棧)吼虎。如上可以看出犬钢,當(dāng)我點擊 POP
按鈕的時候又可以將這個頁面進行出棧,又可以回到我們的home
頁面思灰。但是玷犹,通常我們不這么去返回上一個頁面,在上一章的時候就使用Scaffold
的AppBar
中可以直接添加一個返回洒疚,究其根本這個返回最終也是調(diào)用的這個
Navigator.pop(context);
當(dāng)我們需要在返回的時候帶一個返回值的時候歹颓,可以像如下的方式進行使用,那么這個時候就不能使用Scaffold
的AppBar
中的返回了拳亿,因為它是不會返回任何結(jié)果的晴股。
Navigator.pop(context, true);
pushNamed
上面是通過一個動態(tài)的方式去進行路由,我們也可以使用一種靜態(tài)的方式去路由肺魁,那就是pushNamed
电湘,從字面意思就是通過頁面的名字進行路由的,那么這個名字是從哪里來的呢鹅经?這就需要使用我們上面在MaterialApp
中的routes
路由表了寂呛。
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Color(0xFFFF786E),
primaryColorLight: Color(0xFFFF978F),
accentColor: Color(0xFFFFFFFF)
),
home: Start(),
debugShowCheckedModeBanner: false,
routes:{
"scaffold_page":(context)=>ScaffoldTest(),
"snack_page":(context)=> SnackTest(),
"login_page":(context)=> LoginPage(),
"start_page":(context)=> Start(),
}
);
}
}
Navigator.pushNamed(context, "snack_page");
那么,我們能不能攜帶參數(shù)呢瘾晃?當(dāng)然是可以的咯
void _showBerlinWeather() {
Navigator.pushNamed(
context,
'/weather',
arguments: <String, String>{
'city': 'Berlin',
'country': 'Germany',
},
);
}
也能攜帶一個自定義的對象進行遨游~~
class WeatherRouteArguments {
WeatherRouteArguments({ this.city, this.country });
final String city;
final String country;
bool get isGermanCapital {
return country == 'Germany' && city == 'Berlin';
}
}
void _showWeather() {
Navigator.pushNamed(
context,
'/weather',
arguments: WeatherRouteArguments(city: 'Berlin', country: 'Germany'),
);
}
當(dāng)然還有一些其他的方式:
- pushReplacementNamed 和 pushReplacement 替換當(dāng)前頁面
- popAndPushNamed 當(dāng)前頁面出棧贷痪,入棧新的頁面
- pushNamedAndRemoveUntil 和 pushAndRemoveUntil 入棧新頁面并關(guān)閉之前的所有頁面
動畫
在前面gif
圖中我們可以看到在閃屏頁在不同的時間顏色有不同的變化(圖片模糊,效果不明顯)蹦误,還有點擊登錄的時候劫拢,按鈕的樣子也有變化肉津,那么這個是怎么實現(xiàn)的呢?當(dāng)然是我們的動畫了~~
AnimationController
AnimationController
用來控制一個動畫的正向播放舱沧、反向播放和停止動畫等操作妹沙。在默認情況下AnimationController
是按照線性進行動畫播放的。
需要注意的是在使用AnimationController
的時候需要結(jié)合TickerProvider
熟吏,因為只有在TickerProvider
下才能配置AnimationController
中的構(gòu)造參數(shù)vsync
距糖。TickerProvider
是一個抽象類,所以我們一般使用它的實現(xiàn)類TickerProviderStateMixin
和SingleTickerProviderStateMixin
牵寺。
那么悍引,這兩種方式有什么不同呢?
如果整個生命周期中帽氓,只有一個AnimationController
趣斤,那么就使用SingleTickerProviderStateMixin
,因為此種情況下杏节,它的效率相對來說要高很多唬渗。反之,如果有多個AnimationController
奋渔,就是用TickerProviderStateMixin
镊逝。
需要注意的是,如果AnimationController
不需要使用的時候嫉鲸,一定要將其釋放掉撑蒜,不然有可能造成內(nèi)存泄露。
class StartState extends State<Start> with SingleTickerProviderStateMixin {
AnimationController colorController;
@override
void initState() {
// TODO: implement initState
super.initState();
colorController = new AnimationController(
vsync: this, duration: new Duration(seconds: 3));
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: Container(
//省略部分代碼
...
),
);
}
@override
void dispose() {
// TODO: implement dispose
colorController.dispose();
super.dispose();
}
}
Animation
有了動畫控制器之后玄渗,就需要我們的動畫效果了哦座菠。但是我們可以發(fā)現(xiàn)Animation
本身是個抽象類,所以我們需要的是它的實現(xiàn)類藤树。我們可以直接使用Tween
或者它的子類去實現(xiàn)一個Animation
浴滴,在AnimationController
中提供了一個drive
方法,這個是用來干什么的呢岁钓?這個是用來鏈接一個Tween
到Animation
并返回一個Animation
的實例升略。
Animation<Alignment> _alignment1 = _controller.drive(
AlignmentTween(
begin: Alignment.topLeft,
end: Alignment.topRight,
),
);
為什么要使用Tween
呢?Tween
就是一個線性的插值器屡限,可以實現(xiàn)一個完整的變化過程
class Tween<T extends dynamic> extends Animatable<T> {
Tween({ this.begin, this.end });
T begin;
T end;
@protected
T lerp(double t) {
assert(begin != null);
assert(end != null);
return begin + (end - begin) * t;
}
@override
T transform(double t) {
if (t == 0.0)
return begin;
if (t == 1.0)
return end;
return lerp(t);
}
@override
String toString() => '$runtimeType($begin \u2192 $end)';
}
Tween
的構(gòu)造提供了兩個參數(shù)品嚣,一個開始bengin
,一個結(jié)束end
钧大,就是說讓動畫可以在這個區(qū)間內(nèi)進行變化翰撑,當(dāng)然它也提供了很多子類,比如:ColorTween
啊央、SizeTween
眶诈、IntTween
和CurveTween
等等
-
ColorTween
可以實現(xiàn)兩個顏色的變化 -
SizeTween
可以實現(xiàn)兩個size
的變化 -
IntTween
可以實現(xiàn)兩個int 值之間的變化 -
CurveTween
可以實現(xiàn)動畫非線性變化
CurvedAnimation
CurvedAnimation
就是將一個曲線(非線性)變化應(yīng)用到另一個動畫涨醋,如果想使用 Curve
應(yīng)用到Tween
就可以直接使用上面所說的CurveTween
,可以不CurvedAnimation
册养。
final Animation<double> animation = CurvedAnimation(
parent: controller,
curve: Curves.ease,
);
這里需要兩個參數(shù)一個是動畫控制东帅,也就是我們的AnimationController
,另一個就是curve
球拦,它描述了到底是按照什么樣的曲線進行變化的。在Curves
中提供了很多的變化過程帐我,有興趣的童鞋可以自己去研究一下~~
這里總結(jié)一下:
- AnimationController 控制整個動畫的播放坎炼,停止等操作
- Tween 動畫的變化區(qū)間
- CurvedAniamtion 控制動畫按照非線性進行變化
閃屏動畫實現(xiàn)
要實現(xiàn)一個動畫的,首先肯定需要上面所說的AniamtionController
和 Animation
拦键,有這個還不夠谣光,還需要一個可以根據(jù)
在閃屏頁面中,我們的動畫是顏色根據(jù)時間不同的進行變化芬为,那肯定會用到我們的Tween
萄金,這里是顏色的變化,所以使用到了ColorTween
媚朦。
@override
void initState() {
// TODO: implement initState
super.initState();
colorController = new AnimationController(
vsync: this, duration: new Duration(seconds: 3));
colorAnimation = colorController
.drive(ColorTween(begin: Color(0xFFFF786E), end: Color(0xFFFFA07A)));
}
一般我們對AniamtionController和Animation的初始化在initState()
方法中氧敢,然后就需要在動畫的運行過程中將widget
進行更新,就會使用到我們的setState()
colorAnimation = colorController
.drive(ColorTween(begin: Color(0xFFFF786E), end: Color(0xFFFFA07A)))
..addListener(() {
setState(() {});
});
那么接下來就是讓整個動畫跑起來了~~
Future<Null> playAnimation() async {
try {
await colorController.forward();
await colorController.reverse();
} on TickerCanceled {}
}
這里使用到了dart
語言中的異步询张,有兩個特點:
-
await
返回一定是Future
孙乖,如果不是會報錯 -
await
所在的方法必須在有async
標(biāo)記的函數(shù)中運行。
上面的意思就是讓動畫先正向進行份氧,然后在反向進行~~
但是發(fā)現(xiàn)動畫寫完之后運行唯袄,但是沒有任何作用,這是因為你沒有將動畫的變化應(yīng)用到widget
上
@override
Widget build(BuildContext context) {
return new Scaffold(
body: Container(
decoration: BoxDecoration(color: colorAnimation.value),
child: Center(
...
//省略無關(guān)代碼
),
),
);
}
在上述代碼中的BoxDecoration(color: colorAnimation.value)
就是將顏色的值作用于整個Container
上蜗帜,所以顏色就隨之變化而變化恋拷。
在動畫結(jié)束的時候不是要進行路由跳轉(zhuǎn)到下一個頁面的嘛?這就需要在對動畫的監(jiān)聽厅缺,當(dāng)動畫結(jié)束的時候就進行跳轉(zhuǎn)蔬顾,就需要修改colorAnimation
colorAnimation = colorController
.drive(ColorTween(begin: Color(0xFFFF786E), end: Color(0xFFFFA07A)))
..addListener(() {
if (colorController.isDismissed) {
Navigator.pushAndRemoveUntil(context,
new MaterialPageRoute(builder: (context) {
return LoginPage();
}), ModalRoute.withName("start_page"));
}
setState(() {});
});
這里需要注意的是,在判斷結(jié)束的時候店归,這里使用的是colorController.isDismissed
阎抒,沒有使用colorController.isCompleted
是因為在正向動畫完成的時候就會調(diào)用,還沒讓這個動畫流程運行完成~~
如果需要完整代碼消痛,就可以來這兒且叁。
登錄動畫實現(xiàn)
這里和上面是一樣的實現(xiàn)動動畫,但是直接使用的是Tween
秩伞,而且使用了另一種將Tween
關(guān)聯(lián)到Animation的方式逞带,而且使用
@override
void initState() {
// TODO: implement initState
super.initState();
_animationController = new AnimationController(
vsync: this, duration: new Duration(milliseconds: 1500));
_buttonLengthAnimation = new Tween<double>(
begin: 312.0,
end: 42.0,
).animate(new CurvedAnimation(
parent: _animationController, curve: new Interval(0.3, 0.6)))
..addListener(() {
if (_buttonLengthAnimation.isCompleted) {
if(isLogin){
Navigator.pushNamedAndRemoveUntil(context, "snack_page",ModalRoute.withName('login_page'));
}else{
showTips("登錄失敗");
}
}
setState(() {});
});
}
這里有一點需要注意欺矫,使用的curve
是Interval
,這個的作用就是根據(jù)你提供的時間區(qū)間進行動畫展示展氓。就如上面定的動畫時間大小是1500ms穆趴,那么只有在1500*0.3 = 500 ms的時候開始,并在1500*0.6=900ms的時候完成遇汞。
那么接下來就直接看改變動畫的對widget
處理
InkWell(
onTap: login,
child: Container(
margin: EdgeInsets.only(top: 30),
height: 42,
width: _buttonLengthAnimation.value,
decoration:BoxDecoration(borderRadius: radius, color: colorWhite),
alignment: Alignment.center,
child: _buttonLengthAnimation.value > 75? new Text("立即登錄",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: colorRegular))
: CircularProgressIndicator( valueColor:
new AlwaysStoppedAnimation<Color>(colorRegular),
strokeWidth: 2,
),
),
),
① 當(dāng)點擊登錄按鈕后未妹,動畫開始進行,并且對這個按鈕的寬度就開始進行變化
width: _buttonLengthAnimation.value,
② 當(dāng)動畫的值還大于75的時候空入,中間就顯示Text
络它,但是如果小于或等于75的時候,那它的child就是一個就是一個圓形的進度CircularProgressIndicator
child: _buttonLengthAnimation.value > 75? new Text("立即登錄",
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: colorRegular))
: CircularProgressIndicator( valueColor:
new AlwaysStoppedAnimation<Color>(colorRegular),
strokeWidth: 2,
),
),
其實這就是整個動畫的過程歪赢,只是其中我做了一個對動畫運行的判斷化戳,當(dāng)?shù)卿浭。妥寗赢嫲粹o回到最初的狀態(tài)埋凯,并提示登錄失敗点楼。如果登錄成功,就直接跳轉(zhuǎn)到新的頁面~~
總結(jié)
在這里規(guī)整一下白对,方便大家整理記憶
- 路由有很多中方式掠廓,可以根據(jù)不同的情況進行選擇,一般常用的就是
push
和pushNamed
躏结,如果是pushNamed
那么一定要在MaterialApp
中設(shè)置路由表却盘。 - 動畫的使用一般需要跟
TickerProvider
配合使用,如果在State
中就可以直接使用它的實現(xiàn)類SingleTickerProviderStateMixin
或TickerProviderStateMixin
媳拴。 - 如果只有一個
AnimationController
就是用SingleTickerProviderStateMixin
黄橘,反之,使用TickerProviderStateMixin
屈溉。 - 動畫的建立跟
AnimationController
塞关、Tween
或CurveAnimation
有關(guān)。 -
AnimationController
在不需要的時候一定要進行釋放dispose
子巾,不然可能會造成內(nèi)存溢出帆赢。