Flutter入门篇(三)- 如何实现登录动画效果

在上一篇的时候,我们讲解了怎么做一个登录界面,但是之后呢?完全是草草结尾的感觉嘛,这不,接下来就是给大家详细说说,这个登录里面不得鸟的故事。先来看一个登录的过程~~

分析

可能上面的gif图不是很真切,这上面展示了两个功能:

  • 颜色变换的闪屏页面
  • 动画效果的登录页面

有没有感觉这样的登录好像还不错呢,哈哈哈,接下来就详细分析一下这其中的玄机~~

路由

一般我们的页面跳转都会涉及到路由,路由就是从一个页面跳转到另一个页面的过程,就比如Android中的Activity或IOS中的ViewController的跳转。

在Flutter中所以的路由都使用Navigator来进行管理的,换句话说它就是让这些本来相对独立的个体形成一个完美的整体。那么Navigator是直接管理的就是页面吗?当然不是,实际上它管理是Route对象,而且提供了管理堆栈的相关方法,比如:

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

虽然能够直接创建一个navigator,但是呢,一般不建议这样直接使用,我们常常通过WidgetsApp或者MaterialApp去创建。还记得第一篇的时候,就跟大家提过,Flutter提供了许多widgets,可帮助您构建遵循Material Design的应用程序。Material应用程序以MaterialApp widget开始, 该widget在应用程序的根部创建了一些有用的widget,其中包括一个Navigator, 它管理由字符串标识的Widget栈(即页面路由栈)。Navigator可以让您的应用程序在页面之间的平滑的过渡。 所以我们的应用启动一般这样写:

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,那么以上四个必须有一个被使用。

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的方式去入栈(或者出栈)。如上可以看出,当我点击 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,不然可能会造成内存溢出。

原文地址:https://blog.51cto.com/10687520/2416858

时间: 2024-08-03 13:14:10

Flutter入门篇(三)- 如何实现登录动画效果的相关文章

Flutter入门篇(二)- 第一个APP

在上一篇文章中以简单的方式对Flutter自己提供的演示进行了一个简单的分析,当然那是远远不够.本来打算为大家带来官网上的无限下拉刷新的案例,但是发现这里的有些东西实在是太超前了,作为Flutter入门篇,当然不能这么随意,以为了让大家都能够学有所得,所以今天给大家带来了自己手撸的一个登录. 简单分析布局 我们都知道,一个简单的登录需要至少需要3步: 输入账号 输入密码 点击登录 那么我们的布局也就至少需要3个widget,为什么说至少呢?因为往往布局使用的widget都是大于操作步骤的.这里跟

【SSRS】入门篇(三) -- 为报表定义数据集

原文:[SSRS]入门篇(三) -- 为报表定义数据集 通过前两篇文件 [SSRS]入门篇(一) -- 创建SSRS项目 和 [SSRS]入门篇(二) -- 建立数据源 后, 我们建立了一个SSRS项目,并取得数据源,那么接下来做的就是知道报表要显示什么数据了,这一步可以通过建立数据集来实现. 1.解决方案资源管理器 ->右键选择共享数据集 ->添加新数据集: 2.在共享数据集属性窗口输入数据集名称:AdventureWorksDataset:数据源选择之前建立的:AdventureWorks

8.17_Linux之bash shell脚本编程入门篇(三)之循环以及函数function的使用

bash shell脚本编程入门篇(三)之循环 什么是循环执行? 将某代码段重复运行多次 重复运行多少次: 循环次数事先已知 循环次数事先未知 有进入条件和退出条件 相关命令:for, while, until,selet, for命令的使用 作用: 依次将列表中的元素赋值给"变量名"; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束 命令格式: for 变量名 in 列表; do 循环体(正常执行的执行命令) 语句1 语句2 语句3 ... done 列表生成方式: (

Flutter入门篇(一)

距离Google发布Flutter已经有很长一段时间了,又是一门新的技术,那么我们到底是学呢还是学呢还是学呢?不要问我,我不知道,鬼特么知道我这辈子还要学习多少东西.其实新技术的出现也意味着,老技术会面临淘汰危机,而你将面临着失业危机.用一句话来说:你永远不知道意外和惊喜哪个先来~~ 环境搭建 Flutter的安装就不在这里演示了,可以从下面几个网站上学习安装. Flutter官网 Flutter中文网 Flutter社区 这些网站也通过丰富的Flutter学习资料 Flutter的第一个应用

Flutter入门篇

环境搭建 Flutter的安装就不在这里演示了,可以从下面几个网站上学习安装. Flutter官网 Flutter中文网 Flutter社区 这些网站也通过丰富的Flutter学习资料 Flutter的第一个应用 在创建一个Flutter应用后,我们可以看到如下的demo代码.(其中注释是个人翻译,如有不正确请谅解) import 'package:flutter/material.dart'; //应用启动 void main() => runApp(MyApp()); class MyApp

Flutter入门(三)-底部导航+路由

* StatefulWidget 如果想改变页面中的数据就要用到StatefulWidget,之前自定义组件继承的StatelessWidget是不能动态修改页面数据的 //自定义有状态组件 class HomePage extends StatefulWidget { HomePage({Key key}) : super(key: key); @override _HomePageState createState() => _HomePageState(); } class _HomePa

lintcode入门篇三

一. 两数之和 给一个整数数组,找到两个数使得他们的和等于一个给定的数 target. 你需要实现的函数twoSum需要返回这两个数的下标, 并且第一个下标小于第二个下标.注意这里下标的范围是 0 到 n-1. 样例 Example1: 给出 numbers = [2, 7, 11, 15], target = 9, 返回 [0, 1]. Example2: 给出 numbers = [15, 2, 7, 11], target = 9, 返回 [1, 2]. 挑战 Either of the

jQuery自学笔记(三):jQuery动画效果

jQuery隐藏和显示: 使用 hide( ) 和 show( ) 方法来隐藏和显示 HTML 元素: 语法: $(selector).hide(speed,callback); $(selector).show(speed,callback); 可选的 speed 参数规定隐藏/显示的速度,可以取以下值:"slow"."fast" 或毫秒,可选的 callback 参数是隐藏或显示完成后所执行的函数名称. 一个关于调用callback函数的实例: $("

【SSRS】入门篇(四) -- 向报表添加数据

原文:[SSRS]入门篇(四) -- 向报表添加数据 定义好数据集后 [SSRS]入门篇(三) -- 为报表定义数据集 ,就可以开始设计报表了,将要显示在报表的字段.文本框.图像和其他项从工具箱拖放到报表设计图画上,如下图: 1.打开在[SSRS]入门篇(一) -- 创建SSRS项目 建立的报表Sales Orders.rdl: 2.从工具箱把"表"拖放到"设计图画": 注:如果左边没有显示工具箱的话,可以通过"菜单 -> 视图 -> 工具箱&