给 Flutter 界面切换来点特效

本文微信公众号「AndroidTraveler」首发。

背景

我们知道页面之间如果直接切换,会比较生硬,还会让用户觉得很突兀,用户体验不是很好。

因此一般情况下,页面之间的切换为了达到平滑过渡,都会添加动画。

另外,有时候我们不喜欢系统的默认动画,希望能够自定义动画。

基于此,本篇主要讲述如何给 Flutter 的页面切换增加自定义动画。

默认效果

首先我们看看默认效果是怎样的?

16d00b86b773d013?w=450&h=936&f=gif&s=91463

看起来似乎还不错。代码如下:

import 'package:flutter/material.dart';void main() => runApp(MaterialApp(home: MyApp(),));class MyApp extends StatelessWidget {// This widget is the root of your application.@overrideWidget build(BuildContext context) {return _getCenterWidget(RaisedButton(child: Text('Go to next page->'),onPressed: () {Navigator.of(context).push(_createRoute());}));}
}Route _createRoute() {return MaterialPageRoute(builder: (BuildContext context) => Page2());
}class Page2 extends StatelessWidget {@overrideWidget build(BuildContext context) {return _getCenterWidget(Text('Page2'));}
}Widget _getCenterWidget(Widget child) {return Scaffold(appBar: AppBar(),body: Center(child: child,),);
}

可以看到创建了两个页面 MyApp 和 Page2。

第一个页面 MyApp 有一个按钮,第二个页面 Page2 有一个文本。

关键的切换就在 _createRoute() 这个路由创建方法里面。

我们点进去 MaterialPageRoute 源码,可以看到

  @overrideWidget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);}

加上一开始的注释,可以知道这个就是默认的界面切换过渡效果。

/// See also:
///
///  * [PageTransitionsTheme], which defines the default page transitions used
///    by [MaterialPageRoute.buildTransitions].

另外这里可以看到默认的动画时长为 300ms,而且我们不能自定义。

  @overrideDuration get transitionDuration => const Duration(milliseconds: 300);

接下来我们就说说如何自定义我们的界面切换过渡效果,并且自定义动画时长。

自定义动画

1. 设置 PageRouteBuilder

由上面的分析我们知道最关键的地方在创建路由方法 _createRoute() 中。

因此我们首先修改一下,不使用默认的 MaterialPageRoute,我们使用 PageRouteBuilder

Route _createRoute() {return PageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) => Page2(),transitionsBuilder:(context, animation, secondaryAnimation, child) {return child;});
}

可以看到我们通过 pageBuilder 指定路由页面,通过 transitionsBuilder 指定页面过渡效果。

另外说明一下,这里的参数大家不用死记硬背,我们点进去源码一看就知道了,如下:

/// Signature for the function that builds a route's primary contents.
/// Used in [PageRouteBuilder] and [showGeneralDialog].
///
/// See [ModalRoute.buildPage] for complete definition of the parameters.
typedef RoutePageBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);/// Signature for the function that builds a route's transitions.
/// Used in [PageRouteBuilder] and [showGeneralDialog].
///
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child);

如果我们运行代码,由于直接返回 child,所以应该是没有动画效果的。我们运行之后,效果如下:

16d017e77365532d?w=450&h=936&f=gif&s=56567

2. 添加 Tween 和 SlideTransition

默认的过渡效果是从右边往左过来,我们这里自定义的演示效果就从下面往上过渡好了。

需要了解一下的是 Tween 是一个介于开始和结束值的线性插值器。

另外我们跟进上面的 transitionsBuilder 可以知道他的第一个 animation 参数取值为 0.0 到 1.0。

我们这边是从下往上,所以 y 轴的偏移就是由 1.0 到 0.0,表示竖直方向距离顶部一整个页面到不存在偏移(已经在顶部)。

因此关于 Tweenanimation 我们可以得到:

var begin = Offset(0.0, 1.0);
var end = Offset(0.0, 0.0);
var tween = Tween(begin: begin, end: end);
var offsetAnimation = animation.drive(tween);

因为我们是要实现滑动,因此将这个确定好的偏移动画通过 SlideTransition 处理并返回,可以得到修改后的路由代码如下:

Route _createRoute() {return PageRouteBuilder(transitionDuration: Duration(seconds: 5),pageBuilder: (context, animation, secondaryAnimation) => Page2(),transitionsBuilder:(context, animation, secondaryAnimation, child) {var begin = Offset(0.0, 1.0);var end = Offset(0.0, 0.0);var tween = Tween(begin: begin, end: end);var offsetAnimation = animation.drive(tween);return SlideTransition(position: offsetAnimation,child: child,);});
}

效果如下:

16d045c7e27cd7a2?w=450&h=936&f=gif&s=57322

看到上面效果,可能有小伙伴会有疑问。

问题一:你打开页面是从下到上我可以理解,但是返回为什么是反过来的从上到下呢?

我们跟进去 transitionsBuilder 的源码,可以看到

  /// Used to build the route's transitions.////// See [ModalRoute.buildTransitions] for complete definition of the parameters.final RouteTransitionsBuilder transitionsBuilder;

我们跟进去(通过点击)注释里面的 buildTransitions,可以看到 animation 的说明如下:

  ///  * [animation]: When the [Navigator] pushes a route on the top of its stack,///    the new route's primary [animation] runs from 0.0 to 1.0. When the [Navigator]///    pops the topmost route this animation runs from 1.0 to 0.0.

可以看到入栈和出栈的动画效果是相反的,而这个也符合我们的认知。

问题二:现在的效果是从下到上,如果我要实现从上到下,是不是将 begin 和 end 的 Offset 交换一下就可以?

其实如果你理解我上面说的这句话

我们这边是从下往上,所以 y 轴的偏移就是由 1.0 到 0.0,表示竖直方向距离顶部一整个页面到不存在偏移(已经在顶部)。

你就会知道,改成从 0.0 到 1.0 会是什么情况。

我们改一下,通过实际演示效果来说明。

修改后的代码如下:

Route _createRoute() {return PageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) => Page2(),transitionsBuilder:(context, animation, secondaryAnimation, child) {var begin = Offset(0.0, 0.0);var end = Offset(0.0, 1.0);var tween = Tween(begin: begin, end: end);var offsetAnimation = animation.drive(tween);return SlideTransition(position: offsetAnimation,child: child,);});
}

仅仅是 begin 和 end 的 Offset 做了交换。

运行效果如下:
16d046fc4609c102?w=450&h=936&f=gif&s=38056

虽然能够看出一点端倪,但是我们前面讲过,默认动画时长是 300 ms,所以比较快,这样不好分析。

我们可以通过 PageRouteBuildertransitionDuration 属性来设置动画的时长。

我们设置 3s 来看下效果,代码如下:

Route _createRoute() {return PageRouteBuilder(transitionDuration: Duration(seconds: 3),pageBuilder: (context, animation, secondaryAnimation) => Page2(),transitionsBuilder:(context, animation, secondaryAnimation, child) {var begin = Offset(0.0, 0.0);var end = Offset(0.0, 1.0);var tween = Tween(begin: begin, end: end);var offsetAnimation = animation.drive(tween);return SlideTransition(position: offsetAnimation,child: child,);});
}

运行效果如下:
16d04c1a85a86b51?w=450&h=936&f=gif&s=85724

看到了吧,确实是反过来了,从一开始距离顶部为 0.0,到距离顶部 1.0(100%)。

那么如果我想实现从上到下进入怎么办呢?

我们给一张图,相信看完你就懂了。

16d04c854ce490d6?w=1572&h=2034&f=png&s=167517

从这张图我们知道,如果我们从下往上,y 应该从 1.0 变到 0.0。如果要从上往下,y 应该从 -1.0 变到 0.0。

所以我们修改代码,如下:

Route _createRoute() {return PageRouteBuilder(transitionDuration: Duration(seconds: 3),pageBuilder: (context, animation, secondaryAnimation) => Page2(),transitionsBuilder:(context, animation, secondaryAnimation, child) {var begin = Offset(0.0, -1.0);var end = Offset(0.0, 0.0);var tween = Tween(begin: begin, end: end);var offsetAnimation = animation.drive(tween);return SlideTransition(position: offsetAnimation,child: child,);});
}

运行效果为:
16d04cbab8364a18?w=450&h=936&f=gif&s=83888

3. 通过 CurveTween 来点加速度

当我们将动画时长设置为 3s 之后,我们可以明显的看到我们的动画速度似乎是匀速的。

那么如果我想修改下动画的速度,比如进来快,结束慢,可不可以呢?

答案是肯定的。

我们通过 CurveTween 可以来实现这个需求。

使用的重点在于 curve 的选择,所以我们要选择哪种 curve 呢?

其实 Flutter 我比较喜欢的一个点就是代码注释详细,并且还有 demo 演示。

我们进入 Curves 源码,以 Curves.ease 为例:

  /// A cubic animation curve that speeds up quickly and ends slowly.////// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}static const Cubic ease = Cubic(0.25, 0.1, 0.25, 1.0);

注释说了启动快,结束慢,而且还有一个 mp4 链接,点击可以看到如下效果:

16d04d278c500b97?w=462&h=177&f=gif&s=120549

我们可以看出它的变化趋势,通过斜率可以看出前期快,后期慢,而且右边还有四种动画的效果预览。

我们设置 CurveTween 代码如下:

var curveTween = CurveTween(curve: Curves.ease);

可以看到很简单,选择一种你想要的变化趋势即可。

4. 组合 Tween 和 CurveTween

这个也比较简单,通过 Tween 自带的 chain 方法即可,如下:

var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: Curves.ease));

另外一般 Offset(0.0, 0.0) 可以直接写为 Offset.zero。

修改后代码为:

Route _createRoute() {return PageRouteBuilder(transitionDuration: Duration(seconds: 3),pageBuilder: (context, animation, secondaryAnimation) => Page2(),transitionsBuilder:(context, animation, secondaryAnimation, child) {var begin = Offset(0.0, 1.0);var end = Offset.zero;var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: Curves.ease));return SlideTransition(position: animation.drive(tween),child: child,);});
}

运行效果如下:
16d052f86ab4e5e3?w=450&h=939&f=gif&s=163705

5. 完整例子

有了上面的基础,我们就可以将四个方向的动画效果都加上,当然我们这边就不延时了。另外为了演示方便,就直接打开后 delay 1s 返回。

代码如下:

import 'package:flutter/material.dart';void main() => runApp(MaterialApp(home: MyApp(),));class MyApp extends StatelessWidget {// This widget is the root of your application.@overrideWidget build(BuildContext context) {return _getCenterWidget(Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[_getBtn(context, 'right in',Tween(begin: Offset(1.0, 0.0), end: Offset.zero)),_getBtn(context, 'left in',Tween(begin: Offset(-1.0, 0.0), end: Offset.zero)),_getBtn(context, 'bottom in',Tween(begin: Offset(0.0, 1.0), end: Offset.zero)),_getBtn(context, 'top in',Tween(begin: Offset(0.0, -1.0), end: Offset.zero)),],));}
}Widget _getBtn(BuildContext context, String textContent, Tween<Offset> tween) {return RaisedButton(child: Text(textContent),onPressed: () {Navigator.of(context).push(_createRoute(tween));});
}Route _createRoute(Tween<Offset> tween) {return PageRouteBuilder(pageBuilder: (context, animation, secondaryAnimation) => Page2(),transitionsBuilder: (context, animation, secondaryAnimation, child) {return SlideTransition(position:animation.drive(tween.chain(CurveTween(curve: Curves.ease))),child: child,);});
}class Page2 extends StatelessWidget {@overrideWidget build(BuildContext context) {Future.delayed(Duration(seconds: 1), () {Navigator.of(context).pop();});return _getCenterWidget(Text('Page2'));}
}Widget _getCenterWidget(Widget child) {return Scaffold(appBar: AppBar(),body: Center(child: child,),);
}

效果如下:
16d053cdc57d316b?w=450&h=939&f=gif&s=178845

结语

到了这里,基本就把 Flutter 界面之间的过渡说清楚了。

其他的比如旋转、缩放、透明度甚至组合动画,相信有了上面的基础,你也可以自行进行 DIY。

这里附上缩放的效果如下:
16d05aaf5d69c1a1?w=450&h=939&f=gif&s=86640

具体代码见 GitHub:
flutter_page_transition

参考链接:
Animate a page route transition
Tween class

更多阅读:
Flutter 即学即用系列博客
Flutter & Dart

16bc08e14696aba3?w=900&h=500&f=png&s=231181

转载于:https://www.cnblogs.com/nesger/p/11489579.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/247004.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

生成高斯图像

本文转自 http://www.cnblogs.com/tiandsp/archive/2012/02/26/2368533.html clear; m31; n31; imgzeros(m1,n1); imgdouble(img); pi3.1415926; sigma10; for i-(m/2):m/2for j-(n/2):n/2img(im/21,jn/21)(1/(2*pi*sigma*sigma))*exp(-(i*ij*j)/(2*sigma*sigma)); end end i…

高斯模糊的算法(高斯权重)

本文转自&#xff1a;http://www.ruanyifeng.com/blog/2012/11/gaussian_blur.html 通常&#xff0c;图像处理软件会提供”模糊”&#xff08;blur&#xff09;滤镜&#xff0c;使图片产生模糊的效果。 “模糊”的算法有很多种&#xff0c;其中有一种叫做”高斯模糊“&…

序列化流与反序列化流

序列化流与反序列化流 用于从流中读取对象的 操作流 ObjectInputStream 称为 反序列化流 用于向流中写入对象的操作流 ObjectOutputStream 称为 序列化流 特点&#xff1a;用于操作对象。可以将对象写入到文件中&#xff0c;也可以从文件中读取对象。 1 对象序列化流Objec…

图像相似性搜索的原理

本文转自&#xff1a; 相似图片搜索的原理 相似图片搜索的原理&#xff08;二&#xff09; http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html http://www.ruanyifeng.com/blog/2011/07/principle_of_similar_image_search.html http://blog.s…

灰度共生矩阵(GLCM)并计算能量、熵、惯性矩、相关性(matlab)(待总结)

关于灰度共生矩阵的介绍可参考 http://blog.csdn.net/chuminnan2010/article/details/22035751 http://blog.csdn.net/xuezhisd/article/details/8908824 http://blog.csdn.net/xuexiang0704/article/details/8713204 http://cn.mathworks.com/help/images/ref/imlincomb.h…

机器学习之 weka学习(一)weka介绍,安装和配置环境变量

本部分详情可查看博客http://blog.csdn.net/u011067360/article/details/20844443 数据挖掘开源软件&#xff1a;WEKA基础教程 Weka简介&#xff1a; Weka是由新西兰怀卡托大学开发的智能分析系统&#xff08;Waikato Environment for Knowledge Analysis&#xff09; 。在怀…

第一章:线性空间和线性变换

转载于:https://www.cnblogs.com/invisible2/p/11514817.html

国外十大高校人工智能实验室及其代表性人物一览

本文转自&#xff1a; http://toutiao.com/a6293031494186107137/?tt_frommobile_qq&utm_campaignclient_share&appnews_article&utm_sourcemobile_qq&iid4325464459&utm_mediumtoutiao_android 高校建立的实验室与大公司有所不同&#xff0c;其研究项目…

java 基础安装和Tomcat8配置

初识 java&#xff0c;基础安装的说明。 下载 在oracle官网一般在同一个java版本会提供2个版本&#xff0c; 一个是Java SE Development Kit 7u80&#xff0c;此版本包含JDK开发环境版本&#xff1b; 另外一个是 Java SE Runtime Environment 7u80&#xff0c;此为只包含JR…

matlab内存溢出的解决方案

&#xff08;1&#xff09; 增加虚拟内存&#xff1a;cmd -> taskmgr 打开任务管理器&#xff0c;查看物理内存和虚拟内存&#xff0c;可观察matlab在运行过程中是否超过物理内存和虚拟内存。若超过&#xff0c;增加虚拟内存的方法是不可行的。物理内存不足的时候可以通过将…

c++MMMMM:oo

1.union&#xff0c;struct和class的区别 转载于:https://www.cnblogs.com/invisible2/p/11524465.html

matlab调用Java程序时出现 Java.lang.OutOfMemoryErrot: GC overhead limit exceeded

matlab调用Java程序时出现 java.lang.OutOfMemoryError: GC overhead limit exceeded JDK1.6.0_37和JDK_1.7.0_60版本&#xff0c;这2个版本中JVM默认启动的时候-XX:UseGCOverheadLimit&#xff0c;即启用了该特性。这其实是JVM的一种推断&#xff0c;如果垃圾回收耗费了98%的…

第94:受限玻尔兹曼机

转载于:https://www.cnblogs.com/invisible2/p/11565179.html

安装完Ubuntu桌面后要做的(待续)

1. 为了快速而顺畅的更新&#xff0c;打开终端并输入以下命令来让系统使用新软件库&#xff1a; $ sudo apt-get update 2. 更改系统外观和行为 如果你想要更改桌面背景或图标大小&#xff0c;依次打开System Settings –> Appearance –> Look&#xff0c;并对桌面进…

算法第二章上机实践报告

一、实践题目 改写二分搜索算法 二、问题描述 这道题目主要是考验同学们在熟练掌握二分搜索法的前提下&#xff0c;对二分搜索的结构和运用有一个更加深刻的掌握。首先是要了解二分搜索的结构&#xff0c;其次&#xff0c;要了解二分搜索中的分治方法每一个步骤的用意&#xff…

windows远程登录 ubuntu Linux 系统及互连共享桌面

预备工作 #开启防火墙端口 sudo ufw allow 3389#安装ssh sudo apt-get install openssh-server一、windows直连Ubuntu16.04共享桌面 1、打开终端&#xff0c;安装xrdp,vncserver sudo apt-get install xrdp vnc4server xbase-clients2、安装desktop sharing&#xff08;Ubuntu…

RAID详解

一、raid什么意思&#xff1f; RAID是“Redundant Array of Independent Disk”的缩写&#xff0c;中文翻译过来通俗的讲就是磁盘阵列的意思&#xff0c;也就是说RAID就是把硬盘做成一个阵列&#xff0c;而阵列也就是把硬盘进行组合配置起来&#xff0c;做为一个整体进行管理&a…

装windows和Linux系统时找不到硬盘,pe安装系统没有出现磁盘,不能识别磁盘

装win7的时候&#xff0c;我们使用U盘装系统&#xff0c;找不到硬盘&#xff0c; 或者使用光盘装系统时 会出现 缺少所需的CD/DVD驱动器设备驱动程序 然后找遍整个硬盘/光盘也找不到合适的驱动&#xff0c;安装无法继续。 解决方法&#xff1a; ACHI模式下&#xff0c;PE里…

Linux 服务器上建立用户并分配权限

查看用户 whoami #要查看当前登录用户的用户名 who am i #表示打开当前伪终端的用户的用户名 who mom likes who 命令其它常用参数 参数 说明 -a 打印能打印的全部 -d 打印死掉的进程 -m 同am i,mom likes -q 打印当前登录用户数及用户名 -u 打印当前登录用户登录信…

LSTM

具体推导公式为&#xff1a; https://zybuluo.com/hanbingtao/note/581764 转载于:https://www.cnblogs.com/invisible2/p/11593270.html