Flutter入門篇(三)— 如何實現登陸動畫效果

在上一篇的時候,咱們講解了怎麼作一個登陸界面,可是以後呢?徹底是草草結尾的感受嘛,這不,接下來就是給你們詳細說說,這個登陸裏面不得鳥的故事。先來看一個登陸的過程~~java

登陸失敗

分析

可能上面的gif圖不是很真切,這上面展現了兩個功能:git

  • 顏色變換的閃屏頁面
  • 動畫效果的登陸頁面

有沒有感受這樣的登陸好像還不錯呢,哈哈哈,接下來就詳細分析一下這其中的玄機~~github

路由

通常咱們的頁面跳轉都會涉及到路由,路由就是從一個頁面跳轉到另外一個頁面的過程,就好比Android中的Activity或IOS中的ViewController的跳轉。api

在Flutter中因此的路由都使用Navigator來進行管理的,換句話說它就是讓這些原本相對獨立的個體造成一個完美的總體。那麼Navigator是直接管理的就是頁面嗎?固然不是,實際上它管理是Route對象,並且提供了管理堆棧的相關方法,好比:app

  • Navigator.push (入棧)
  • Navigator.pop (出棧)

雖然可以直接建立一個navigator,可是呢,通常不建議這樣直接使用,咱們經常經過WidgetsApp或者MaterialApp去建立。還記得第一篇的時候,就跟你們提過,Flutter提供了許多widgets,可幫助您構建遵循Material Design的應用程序。Material應用程序以MaterialApp widget開始, 該widget在應用程序的根部建立了一些有用的widget,其中包括一個Navigator, 它管理由字符串標識的Widget棧(即頁面路由棧)。Navigator可讓您的應用程序在頁面之間的平滑的過渡。 因此咱們的應用啓動通常這樣寫:less

void main() {
	runApp(MaterialApp(home: MyAppHome()));
}
複製代碼

那麼,home所指向的頁面也就是咱們棧中最底層的路由,那MaterialApp究竟是怎麼建立這個底層路由的呢?它遵循如下幾個原則:異步

const MaterialApp({ Key key, this.navigatorKey, this.home, this.routes = const <String, WidgetBuilder>{}, this.initialRoute, this.onGenerateRoute, this.onUnknownRoute, //省略無關代碼 ... }) 複製代碼
  • 首先使用咱們的home所指向的
  • 若是失敗,那麼就會使用routes路由表
  • 若是路由表爲空,那麼就會調用onGenerateRoute
  • 若是以上全部都失敗了,那麼onUnknownRoute將會被調用

因此說若是要建立Navigator,那麼以上四個必須有一個被使用。async

MaterialPageRoute

通常咱們可使用MaterialPageRoute去進行路由:ide

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的方式去入棧(或者出棧)。如上能夠看出,當我點擊 POP按鈕的時候又能夠將這個頁面進行出棧,又能夠回到咱們的home頁面。可是,一般咱們不這麼去返回上一個頁面,在上一章的時候就使用ScaffoldAppBar中能夠直接添加一個返回,究其根本這個返回最終也是調用的這個函數

Navigator.pop(context);
複製代碼

當咱們須要在返回的時候帶一個返回值的時候,能夠像以下的方式進行使用,那麼這個時候就不能使用ScaffoldAppBar中的返回了,由於它是不會返回任何結果的。

Navigator.pop(context, true);
複製代碼

pushNamed

上面是經過一個動態的方式去進行路由,咱們也可使用一種靜態的方式去路由,那就是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");
複製代碼

那麼,咱們能不能攜帶參數呢?固然是能夠的咯

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'),
  );
}
複製代碼

固然還有一些其餘的方式:

  1. pushReplacementNamed 和 pushReplacement 替換當前頁面
  2. popAndPushNamed 當前頁面出棧,入棧新的頁面
  3. pushNamedAndRemoveUntil 和 pushAndRemoveUntil 入棧新頁面並關閉以前的全部頁面

動畫

在前面gif圖中咱們能夠看到在閃屏頁在不一樣的時間顏色有不一樣的變化(圖片模糊,效果不明顯),還有點擊登陸的時候,按鈕的樣子也有變化,那麼這個是怎麼實現的呢?固然是咱們的動畫了~~

AnimationController

AnimationController用來控制一個動畫的正向播放、反向播放和中止動畫等操做。在默認狀況下AnimationController是按照線性進行動畫播放的。 須要注意的是在使用AnimationController的時候須要結合TickerProvider,由於只有在TickerProvider下才能配置AnimationController中的構造參數vsyncTickerProvider是一個抽象類,因此咱們通常使用它的實現類TickerProviderStateMixinSingleTickerProviderStateMixin

那麼,這兩種方式有什麼不一樣呢? 若是整個生命週期中,只有一個AnimationController,那麼就使用SingleTickerProviderStateMixin,由於此種狀況下,它的效率相對來講要高不少。反之,若是有多個AnimationController,就是用TickerProviderStateMixin

須要注意的是,若是AnimationController不須要使用的時候,必定要將其釋放掉,否則有可能形成內存泄露。

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

有了動畫控制器以後,就須要咱們的動畫效果了哦。可是咱們能夠發現Animation自己是個抽象類,因此咱們須要的是它的實現類。咱們能夠直接使用Tween或者它的子類去實現一個Animation,在AnimationController中提供了一個drive方法,這個是用來幹什麼的呢?這個是用來連接一個TweenAnimation並返回一個Animation的實例。

Animation<Alignment> _alignment1 = _controller.drive(
  AlignmentTween(
    begin: Alignment.topLeft,
    end: Alignment.topRight,
  ),
);
複製代碼

爲何要使用Tween呢?Tween就是一個線性的插值器,能夠實現一個完整的變化過程

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的構造提供了兩個參數,一個開始bengin ,一個結束end,就是說讓動畫能夠在這個區間內進行變化,固然它也提供了不少子類,好比:ColorTweenSizeTweenIntTweenCurveTween等等

  • ColorTween 能夠實現兩個顏色的變化
  • SizeTween 能夠實現兩個size的變化
  • IntTween 能夠實現兩個int 值之間的變化
  • CurveTween 能夠實現動畫非線性變化

CurvedAnimation

CurvedAnimation就是將一個曲線(非線性)變化應用到另外一個動畫,若是想使用 Curve應用到Tween就能夠直接使用上面所說的CurveTween,能夠不CurvedAnimation

final Animation<double> animation = CurvedAnimation(
  parent: controller,
  curve: Curves.ease,
);
複製代碼

這裏須要兩個參數一個是動畫控制,也就是咱們的AnimationController,另外一個就是curve,它描述了究竟是按照什麼樣的曲線進行變化的。在Curves中提供了不少的變化過程,有興趣的童鞋能夠本身去研究一下~~

動畫關係
這裏總結一下:

  • AnimationController 控制整個動畫的播放,中止等操做
  • Tween 動畫的變化區間
  • CurvedAniamtion 控制動畫按照非線性進行變化

閃屏動畫實現

要實現一個動畫的,首先確定須要上面所說的AniamtionControllerAnimation,有這個還不夠,還須要一個能夠根據 在閃屏頁面中,咱們的動畫是顏色根據時間不一樣的進行變化,那確定會用到咱們的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標記的函數中運行。

上面的意思就是讓動畫先正向進行,而後在反向進行~~ 可是發現動畫寫完以後運行,可是沒有任何做用,這是由於你沒有將動畫的變化應用到widget

@override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: Container(
        decoration: BoxDecoration(color: colorAnimation.value),
        child: Center(
        ...
        //省略無關代碼
        ),
     ),
   );
}
複製代碼

在上述代碼中的BoxDecoration(color: colorAnimation.value)就是將顏色的值做用於整個Container上,因此顏色就隨之變化而變化。

在動畫結束的時候不是要進行路由跳轉到下一個頁面的嘛?這就須要在對動畫的監聽,當動畫結束的時候就進行跳轉,就須要修改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(() {});
          });
複製代碼

這裏須要注意的是,在判斷結束的時候,這裏使用的是colorController.isDismissed,沒有使用colorController.isCompleted是由於在正向動畫完成的時候就會調用,還沒讓這個動畫流程運行完成~~

若是須要完整代碼,就能夠來這兒

登陸動畫實現

這裏和上面是同樣的實現動動畫,可是直接使用的是Tween,並且使用了另外一種將Tween關聯到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(() {});
    });
}
複製代碼

這裏有一點須要注意,使用的curveInterval,這個的做用就是根據你提供的時間區間進行動畫展現。就如上面定的動畫時間大小是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,
            ),
    ),
),
複製代碼

① 當點擊登陸按鈕後,動畫開始進行,而且對這個按鈕的寬度就開始進行變化

width: _buttonLengthAnimation.value,
複製代碼

② 當動畫的值還大於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,
            ),
    ),
複製代碼

其實這就是整個動畫的過程,只是其中我作了一個對動畫運行的判斷,當登陸失敗,就讓動畫按鈕回到最初的狀態,並提示登陸失敗。若是登陸成功,就直接跳轉到新的頁面~~

總結

在這裏規整一下,方便你們整理記憶

  • 路由有不少中方式,能夠根據不一樣的狀況進行選擇,通常經常使用的就是pushpushNamed,若是是pushNamed那麼必定要在MaterialApp中設置路由表。
  • 動畫的使用通常須要跟TickerProvider配合使用,若是在State中就能夠直接使用它的實現類SingleTickerProviderStateMixinTickerProviderStateMixin
  • 若是隻有一個AnimationController就是用SingleTickerProviderStateMixin,反之,使用TickerProviderStateMixin
  • 動畫的創建跟AnimationControllerTweenCurveAnimation有關。
  • AnimationController在不須要的時候必定要進行釋放dispose,否則可能會形成內存溢出。
相關文章
相關標籤/搜索