写点什么

Flutter 学习之布局、交互、动画,字节跳动学习笔记

用户头像
Android架构
关注
发布于: 刚刚

}


//延迟 3 秒后刷新 Future initData() async{await Future.delayed(Duration(seconds: 3),(){setState(() {//用生成器给所有元素赋初始值 list = List.generate(20, (i){return i;});});});}}


一开始先创建并初始化长度是 20 的List集合,ListView根据这个集合长度来构建对应数目的Item项,上面代码是初始化 3 秒后才刷新数据,并加了标记isfresh是否加载刷新,Scafford代码如下:


//ListView ItemWidget _itemColumn(BuildContext context,int index){if(index <list.length){return Column(children: <Widget>[cardWidget,],);


}


}return new Scaffold(appBar: new AppBar(title: new Text('Flutter Demo'),),body: RefreshIndicator(//ListView 提供一个 builder 属性 child: ListView.builder(//集合数目 itemCount: list.length,//itemBuilder 是一个匿名回调函数,有两个参数,BuildContext 和迭代器 index//和 ListView 的 Item 项类似 迭代器从 0 开始 每调用一次这个函数,迭代器就会加 1itemBuilder: _itemColumn,),onRefresh: _onRefresh,),);}


下面把下拉刷新方法逻辑简单加一下,我这边只是重新将集合清空,然后重新添加 8 条数据,只是为了看刷新效果而儿:


//下拉刷新方法 Future<Null> _onRefresh() async {//写逻辑 延迟 3 秒后执行刷新//刷新把 isfresh 改为 trueisfresh = true;await Future.delayed(Duration(seconds: 3),(){setState(() {//数据清空再重新添加 8 条数据 list.clear();list.addAll(List.generate(8, (i){return i;}));});});}


为了看到刷新效果,当刷新的时候,因为isfresh为 true,收藏图标??改为红色,否则是黑色:


//布局三开始第一行 Widget LayoutThreeOne = Row(children: <Widget>[Expanded(child: Row(children: <Widget>[Text('作者:'),Text('HuYounger',style: getTextStyle(Colors.redAccent[400], 14, false),),],)),//收藏图标 改为以下 getPaddingfromLTRB(Icon(Icons.favorite,color:isfresh ? Colors.red : Colors.black),r:10.0),//分享图标 Icon(Icons.share,color:Colors.black),],);


效果如下:


4.2.上拉加载

Flutter中加载更多的组件没有是提供的,那就要自己来实现,我的思路是,当监听滑到底部时,到底底部就要做加载处理。而ListViewScrollController这个属性来控制ListView的滑动事件,在initState添加监听是否到达底部,并且添加上拉加载更多方法:


class HomeWidget extends State<HomeStateful> {


//ListView 控制器 ScrollController _controller = ScrollController();//这个方法只会调用一次,在这个 Widget 被创建之后,必须调用 super.initState()@overridevoid initState(){super.initState();//初始化数据 initData();//添加监听_controller.addListener((){//这里判断滑到底部第一个条件就可以了,加上不在刷新和不是上滑加载 if(_controller.position.pixels == _controller.position.maxScrollExtent){//滑到底部了_onGetMoreData();}});}}


//上拉加载更多方法 每次加 8 条数据 Future _onGetMoreData() async{print('进入上拉加载方法');isfresh = false;if(list.length <=30){await Future.delayed(Duration(seconds: 2),(){setState(() {//加载数据//这里添加 8 项 list.addAll(List.generate(8, (i){return i;}));


});});


}}


//State 删除对象时调用 Dispose,这是永久性 移除监听 清理环境 @overridevoid dispose(){super.dispose();_controller.dispose();}


最后在ListView.builde下增加controller属性:


return new Scaffold(appBar: new AppBar(title: new Text('Flutter Demo'),),body: RefreshIndicator(onRefresh: _onRefresh,//ListView 提供一个 builder 属性 child: ListView.builder(...itemBuilder: _itemColumn,//控制器 上拉加载 controller: _controller,),),);


上面代码已经实现下拉加载更多,但是没有任何交互,我们知道,软件当上拉加载都会有提示,那下面增加一个加载更多的提示圆圈:


...//是否隐藏底部 bool isBottomShow = false;//加载状态 String statusShow = '加载中...';...


//上拉加载更多方法 Future _onGetMoreData() async{print('进入上拉加载方法');isBottomShow = false;isfresh = false;if(list.length <=30){await Future.delayed(Duration(seconds: 2),(){setState(() {//加载数据//这里添加 8 项 list.addAll(List.generate(8, (i){return i;}));});});}else{//假设已经没有数据了 await Future.delayed(Duration(seconds: 3),(){setState(() {isBottomShow = true;});});


}


//显示'加载更多',显示在界面上 Widget _GetMoreDataWidget(){return Center(child: Padding(padding:EdgeInsets.all(12.0),// Offstage 就是实现加载后加载提示圆圈是否消失 child:new Offstage(// widget 根据 isBottomShow 这个值来决定显示还是隐藏 offstage: isBottomShow,child:Row(mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[Text(//根据状态来显示什么 statusShow,style:TextStyle(color: Colors.grey[300],fontSize: 16.0,)),//加载圆圈 CircularProgressIndicator(strokeWidth: 2.0,)],),)


),);}


可以看到,上面用了OffstageWidget 里的offstage属性来控制加载提示圆圈是否显示,isBottomShow如果是 true,加载圆圈就会消失,false 就会显示。并且statusShow来显示加载中的状态,然后要在集合长度加一,也就是给ListView添加尾部:


return new Scaffold(appBar: new AppBar(title: new Text('Flutter Demo'),),body: RefreshIndicator(onRefresh: _onRefresh,//ListView 提供一个 builder 属性 child: ListView.builder(//数目 加上尾部加载更多 list 就要加 1 了 itemCount: list.length + 1,//itemBuilder 是一个匿名回调函数,有两个参数,BuildContext 和迭代器 index//和 ListView 的 Item 项类似 迭代器从 0 开始 每调用一次这个函数,迭代器就会加 1itemBuilder: _itemColumn,//控制器 controller: _controller,),),);


效果如下图:


4.3.ListView.separated

基本还可以,把上滑加载的提示圈加上去了,做到这里,我在想,有时候ListView并不是每一条Item养生都是一样的,哪有没有属性是设置在不同位置插入不同的Item呢?答案是有的,那就是ListView.separatedListView.separated就是在 Android 中adapter不同类型的itemView。用法如下:


body: new ListView.separated(//普通项 itemBuilder: (BuildContext context, int index) {return new Text("text $index");},//插入项 separatorBuilder: (BuildContext context, int index) {return new Container(height: 1.0, color: Colors.red);},//数目 itemCount: 40),


自己例子实现一下:


//ListView item 布局二 Widget cardWidget_two = Card(child: Container(//alignment: Alignment(0.0, 0.0),height: 160.0,color: Colors.white,padding: EdgeInsets.all(10.0),child: Center(// 布局一 child: ColumnWidget,)),);


return new Scaffold(appBar: new AppBar(title: new Text('Flutter Demo'),),body: RefreshIndicator(onRefresh: _onRefresh,//ListView 提供一个 builder 属性 child: ListView.separated(itemBuilder: (BuildContext context,int index){return _itemColumn(context,index);


},separatorBuilder: (BuildContext context,int index){return Column(children: <Widget>[cardWidget_two],);},itemCount: list.length + 1,controller: _controller,),


把一开始实现的布局一作为item插入ListView,效果如下:



发现上面的代码是两个不同类型item项交互插入在ListView中,下面试一下每隔 3 项才插一条试试看:


return new Scaffold(appBar: new AppBar(title: new Text('Flutter Demo'),),body: RefreshIndicator(onRefresh: _onRefresh,//ListView 提供一个 builder 属性 child: ListView.separated(itemBuilder: (BuildContext context,int index){return _itemColumn(context,index);


},separatorBuilder: (BuildContext context,int index){return Column(children: <Widget>[(index + 1) % 3 == 0 ? cardWidget_two : Container()//cardWidget_two],);},itemCount: list.length + 1,controller: _controller,),);


效果如下:


三、交互

1.自带交互的控件

Flutter中,自带如点击事件的控件有RaisedButtonIconButtonOutlineButtonCheckboxSnackBarSwitch等,如下面给OutlineButton添加点击事件:


body:Center(child: OutlineButton(child: Text('点击我'),onPressed: (){Fluttertoast.showToast(msg: '你点击了 FlatButton',toastLength: Toast.LENGTH_SHORT,gravity: ToastGravity.CENTER,timeInSecForIos: 1,);}),),


上面代码就可以捕捉OutlineButton的点击事件。

2.不自带交互的控件

很多控件不像RaisedButtonOutlineButton等已经对presses(taps)或手势做出了响应。那么如果要监听这些控件的手势就需要用另一个控件GestureDetector,那看看源码GestureDetector支持哪些手势:


GestureDetector({Key key,this.child,this.onTapDown,//按下,每次和屏幕交互都会调用 this.onTapUp,//抬起,停止触摸时调用 this.onTap,//点击,短暂触摸屏幕时调用 this.onTapCancel,//取消 触发了 onTapDown,但没有完成 onTapthis.onDoubleTap,//双击,短时间内触摸屏幕两次 this.onLongPress,//长按,触摸时间超过 500ms 触发 this.onLongPressUp,//长按松开 this.onVerticalDragDown,//触摸点开始和屏幕交互,同时竖直拖动按下 this.onVerticalDragStart,//触摸点开始在竖直方向拖动开始 this.onVerticalDragUpdate,//触摸点每次位置改变时,竖直拖动更新 this.onVerticalDragEnd,//竖直拖动结束 this.onVerticalDragCancel,//竖直拖动取消 this.onHorizontalDragDown,//触摸点开始跟屏幕交互,并水平拖动 this.onHorizontalDragStart,//水平拖动开始,触摸点开始在水平方向移动 this.onHorizontalDragUpdate,//水平拖动更新,触摸点更新 this.onHorizontalDragEnd,//水平拖动结束触发 this.onHorizontalDragCancel,//水平拖动取消 onHorizontalDragDown 没有成功触发//onPan 可以取代 onVerticalDrag 或者 onHorizontalDrag,三者不能并存 this.onPanDown,//触摸点开始跟屏幕交互时触发 this.onPanStart,//触摸点开始移动时触发 this.onPanUpdate,//屏幕上的触摸点位置每次改变时,都会触发这个回调 this.onPanEnd,//pan 操作完成时触发 this.onPanCancel,//pan 操作取消//onScale 可以取代 onVerticalDrag 或者 onHorizontalDrag,三者不能并存,不能与 onPan 并存 this.onScaleStart,//触摸点开始跟屏幕交互时触发,同时会建立一个焦点为 1.0this.onScaleUpdate,//跟屏幕交互时触发,同时会标示一个新的焦点 this.onScaleEnd,//触摸点不再跟屏幕交互,标示这个 scale 手势完成 this.behavior,this.excludeFromSemantics = false})


这里注意:onVerticalXXX/onHorizontalXXXonPanXXX不能同时设置,如果同时需要水平、竖直方向的移动,设置onPanXXX。直接上例子:

2.1.onTapXXX

child: GestureDetector(child: Container(width: 300.0,height: 300.0,color:Colors.red,),onTapDown: (d){print("onTapDown");},onTapUp: (d){print("onTapUp");},onTap:(){print("onTap");},onTapCancel: (){print("onTaoCancel");},)


点了一下,并且抬起,结果是:


I/flutter (16304): onTapDownI/flutter (16304): onTapUpI/flutter (16304): onTap 先触发 onTapDown 然后 onTapUp 继续 onTap

2.2.onLongXXX

//手势测试 Widget gestureTest = GestureDetector(child: Container(width: 300.0,height: 300.0,color:Colors.red,),onDoubleTap: (){print("双击 onDoubleTap");},onLongPress: (){print("长按 onLongPress");},onLongPressUp: (){print("长按抬起 onLongPressUP");},


);


实际结果:


I/flutter (16304): 长按 onLongPressI/flutter (16304): 长按抬起 onLongPressUPI/flutter (16304): 双击 onDoubleTap

2.3.onVerticalXXX

//手势测试 Widget gestureTest = GestureDetector(child: Container(width: 300.0,height: 300.0,color:Colors.red,),onVerticalDragDown: (){print("竖直方向拖动按下 onVerticalDragDown:"+.globalPosition.toString());},onVerticalDragStart: (){print("竖直方向拖动开始 onVerticalDragStart"+.globalPosition.toString());},onVerticalDragUpdate: (){print("竖直方向拖动更新 onVerticalDragUpdate"+.globalPosition.toString());},onVerticalDragCancel: (){print("竖直方向拖动取消 onVerticalDragCancel");},onVerticalDragEnd: (_){print("竖直方向拖动结束 onVerticalDragEnd");},


);


输出结果:


I/flutter (16304): 竖直方向拖动按下 onVerticalDragDown:Offset(191.7, 289.3)I/flutter (16304): 竖直方向拖动开始 onVerticalDragStartOffset(191.7, 289.3)I/flutter (16304): 竖直方向拖动更新 onVerticalDragUpdateOffset(191.7, 289.3)I/flutter (16304): 竖直方向拖动更新 onVerticalDragUpdateOffset(191.7, 289.3)I/flutter (16304): 竖直方向拖动更新 onVerticalDragUpdateOffset(191.7, 289.3)I/flutter (16304): 竖直方向拖动更新 onVerticalDragUpdateOffset(191.7, 289.3)I/flutter (16304): 竖直方向拖动更新 onVerticalDragUpdateOffset(191.7, 289.3)I/flutter (16304): 竖直方向拖动更新 onVerticalDragUpdateOffset(191.3, 290.0)I/flutter (16304): 竖直方向拖动更新 onVerticalDragUpdateOffset(191.3, 291.3)I/flutter (16304): 竖直方向拖动结束 onVerticalDragEnd

2.4.onPanXXX

//手势测试 Widget gestureTest = GestureDetector(child: Container(width: 300.0,height: 300.0,color:Colors.red,),onPanDown: (){print("onPanDown");},onPanStart: (){print("onPanStart");},onPanUpdate: (){print("onPanUpdate");},onPanCancel: (){print("onPanCancel");},onPanEnd: (){print("onPanEnd");},


);


无论竖直拖动还是横向拖动还是一起来,结果如下:


I/flutter (16304): onPanDownI/flutter (16304): onPanStartI/flutter (16304): onPanUpdateI/flutter (16304): onPanUpdateI/flutter (16304): onPanEnd

2.5.onScaleXXX

//手势测试 Widget gestureTest = GestureDetector(child: Container(width: 300.0,height: 300.0,color:Colors.red,),onScaleStart: (){print("onScaleStart");},onScaleUpdate: (){print("onScaleUpdate");},onScaleEnd: (_){print("onScaleEnd");


);


无论点击、竖直拖动、水平拖动,结果如下:


I/flutter (16304): onScaleStartI/flutter (16304): onScaleUpdateI/flutter (16304): onScaleUpdateI/flutter (16304): onScaleUpdateI/flutter (16304): onScaleUpdateI/flutter (16304): onScaleUpdateI/flutter (16304): onScaleUpdateI/flutter (16304): onScaleUpdateI/flutter (16304): onScaleEnd

3.原始指针事件

除了GestureDetector能够监听触摸事件外,Pointer代表用户与设备屏幕交互的原始数据,也就是也能监听手势:


  1. PointerDownEvent:指针接触到屏幕的特定位置

  2. PointerMoveEvent:指针从屏幕上的一个位置移动到另一个位置

  3. PointMoveEvent:指针停止接触屏幕

  4. PointUpEvent:指针停止接触屏幕

  5. PointerCancelEvent:指针的输入事件不再针对此应用


上代码:


//PointerWidget TestContainer = Listener(child:Container(width: 300.0,height: 300.0,color:Colors.red,),onPointerDown: (event){print("onPointerDown");},onPointerUp: (event){print("onPointerUp");},onPointerMove: (event){print("onPointerMove");},onPointerCancel: (event){print("onPointerCancel");},


);


在屏幕上点击,或者移动:


I/flutter (16304): onPointerDownI/flutter (16304): onPointerMoveeI/flutter (16304): onPointerMoveI/flutter (16304): onPointerMovesI/flutter (16304): onPointerMoveI/flutter (16304): onPointerUp


发现也是可以监听手势的。

4.路由(页面)跳转

Android原生中,页面跳转是通过startActvity()来跳转不同页面,而在Flutter就不一样。Flutter中,跳转页面有两种方式:静态路由方式和动态路由方式。在Flutter管理多个页面有两个核心概念和类:RouteNavigator。一个route是一个屏幕或者页面的抽象,Navigator是管理routeWidgetNavigator可以通过route入栈和出栈来实现页面之间的跳转。

4.1.静态路由

4.1.1.配置路由

在原页面配置路由跳转,就是在MaterialApp里设置每个route对应的页面,注意:一个 app 只能有一个材料设计(MaterialApp),不然返回上一个页面会黑屏。代码如下:


//入口页面 class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(//静态路由方式 配置初始路由 initialRoute: '/',routes: {//默认走这个条件/'/':(context){return HomeStateful();},//新页面路由'/mainnewroute':(context){return new newRoute();}},//主题色 theme: ThemeData(//设置为红色 primarySwatch: Colors.red),//配置了初始路由,下面就不需要了//home: HomeStateful(),);}}


因为配置了初始路由,所以home:HomeStateful就不用配置了。

4.1.2.点击跳转

//如果新页面不在同一个类中,记得把它导入 import 'mainnewroute.dart';class HomeStateful extends StatefulWidget{@overrideState<StatefulWidget> createState(){return new HomeWidget();}


}


class HomeWidget extends State<HomeStateful> {@overrideWidget build(BuildContext context) {...//PointerWidget TestContainer = Listener(child:Container(width: 300.0,height: 300.0,color:Colors.red,child: RaisedButton(child: Text('点击我'),onPressed: (){//页面跳转方法 Navigator.of(context).pushNamed('/mainnewroute');}),),);return new Scaffold(appBar: new AppBar(title: new Text('Flutter Demo'),),body:Center(child: TestContainer,),);}}


RaisedButton配置了点击方法,上面用了Navigator.of(context).pushNamed('/mainnewroute'),执行到这句,路由会找routes有没有配置/mainnewroute,有的话,就会根据配置跳到新的页面。

4.1.3.配置新页面

新页面,我在lib下建立一个新的文件(页面)mainfourday.dart,很简单:


import 'package:flutter/material.dart';class newRoute extends StatelessWidget{


@overrideWidget build(BuildContext context){return HomeWidget();//注意:不需要 MaterialApp// return MaterialApp(// theme: ThemeData(// //设置为 hongse// primarySwatch: Colors.red),// home: HomeWidget(),// );


}}


class HomeWidget extends StatelessWidget{


@overrideWidget build(BuildContext context){return Scaffold(appBar: AppBar(title: Text('new Route'),),body: Center(child:RaisedButton(child: Text('返回'),onPressed: (){//这是关闭页面 Navigator.pop(context);}),// child: Text('这是新的页面'),),);}}


最终效果如下:


4.2.动态路由

下面说一下跳转页面的第二种方式,动态路由方式:


child: RaisedButton(child: Text('点击我'),onPressed: (){//Navigator.of(context).pushNamed('/mainnewroute');//动态路由 Navigator.push(context,MaterialPageRoute(builder: (newPage){return new newRoute();}),);}),


效果和上面是一样的。

4.3.页面传递数据

两种方式都是传递参数的,直接上动态路由传递数据代码:


Navigator.push(context,MaterialPageRoute(builder: (newPage){return new newRoute("这是一份数据到新页面");}),);


在新页面改为如下:


import 'package:flutter/material.dart';class newRoute extends StatelessWidget{//接收上一个页面传递的数据 String str;//构造函数 newRoute(this.str);


@overrideWidget build(BuildContext context){return HomeWidget(str);}}


class HomeWidget extends StatelessWidget{String newDate;HomeWidget(this.newDate);


@overrideWidget build(BuildContext context){return Scaffold(appBar: AppBar(title: Text('new Route'),),body: Center(child:RaisedButton(//显示上一个页面所传递的数据 child: Text(newDate),onPressed: (){Navigator.pop(context);}),// child: Text('这是新的页面'),),);}}


静态路由方式传递参数,也就是在newRoute()加上所要传递的参数就可以了


//新页面路由'/mainnewroute':(context){return new newRoute("sdsd");}

4.4.页面返回数据

传递数据给新页面可以了,那么怎样将新页面数据返回上一个页面呢?也是很简单,在返回方法pop加上所要返回的数据即可:


body: Center(child:RaisedButton(//显示上一个页面所传递的数据 child: Text(newDate),onPressed: (){Navigator.pop(context,"这是新页面返回的数据");}),// child: Text('这是新的页面'),),


因为打开页面是异步的,所以页面的结果需要通过一个Future来返回,静态路由方式:


child: RaisedButton(child: Text('点击我'),onPressed: () async {var data = await Navigator.of(context).pushNamed('/mainnewroute');//打印返回来的数据 print(data);}),


动态路由方式:


child: RaisedButton(child: Text('点击我'),onPressed: () async {var data = await Navigator.push(context,MaterialPageRoute(builder: (newPage){return new newRoute("这是一份数据到新页面");}),);//打印返回的值 print(data);}),


两者方式都是可以的。

四、动画

Flutter动画库的核心类是Animation对象,它生成指导动画的值,Animation对象指导动画的当前状态(例如,是开始、停止还是向前或者向后移动),但它不知道屏幕上显示的内容。动画类型分为两类:


  1. 补简动画(Tween),定义了开始点和结束点、时间线以及定义转换时间和速度的曲线。然后由框架计算如何从开始点过渡到结束点。Tween 是一个无状态(stateless)对象,需要 begin 和 end 值。Tween 的唯一职责就是定义从输入范围到输出范围的映射。输入范围通常为 0.0 到 1.0,但这不是必须的。

  2. 基于物理动画,运动被模拟与真实世界行为相似,例如,当你掷球时,它何处落地,取决于抛球速度有多快、球有多重、距离地面有多远。类似地,将连接在弹簧上的球落下(并弹起)与连接到绳子的球放下的方式也是不同。


Flutter中的动画系统基于Animation对象的。widget可以在build函数中读取Animation对象的当前值,并且可以监听动画的状态改变。

1.动画示例

import 'package:flutter/material.dart';import 'package:flutter/animation.dart';


void main() {//运行程序 runApp(LogoApp());}


class LogoApp extends StatefulWidget{@overrideState<StatefulWidget> createState(){return new _LogoAppState();}


}


//logoWidget ImageLogo = new Image(image: new AssetImage('images/logo.jpg'),);


//with 是 dart 的关键字,混入的意思,将一个或者多个类的功能天骄到自己的类无需继承这些类//避免多重继承问题//SingleTickerProviderStateMixin 初始化 animation 和 Controller 的时候需要一个 TickerProvider 类型的参数 Vsync//所依混入 TickerProvider 的子类 class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{//动画的状态,如动画开启,停止,前进,后退等 Animation<double> animation;//管理者 animation 对象 AnimationController controller;@overridevoid initState() {// TODO: implement initStatesuper.initState();//创建 AnimationController//需要传递一个 vsync 参数,存在 vsync 时会防止屏幕外动画(//译者语:动画的 UI 不在当前屏幕时)消耗不必要的资源。 通过将 SingleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为 vsync 的值。controller = new AnimationController(//时间是 3000 毫秒 duration: const Duration(milliseconds: 3000),//vsync 在此处忽略不必要的情况 vsync: this,);//补间动画 animation = new Tween(//开始的值是 0begin: 0.0,//结束的值是 200end : 200.0,).animate(controller)//添加监听器..addListener((){//动画值在发生变化时就会调用 setState(() {


});});//只显示动画一次 controller.forward();}@overrideWidget build(BuildContext context){return new MaterialApp(theme: ThemeData(primarySwatch: Colors.red


),home: new Scaffold(appBar: new AppBar(title: Text("动画 demo"),),body:new Center(child: new Container(//宽和高都是根据 animation 的值来变化 height: animation.value,width: animation.value,child: ImageLogo,),),),);}


@overridevoid dispose() {// TODO: implement disposesuper.dispose();//资源释放 controller.dispose();}


}


上面实现了图像在 3000 毫秒间从宽高是 0 变化到宽高是 200,主要分为六部


  1. 混入SingleTickerProviderStateMixin,为了传入vsync对象

  2. 初始化AnimationController对象

  3. 初始化Animation对象,并关联AnimationController对象

  4. 调用AnimationControllerforward开启动画

  5. widget根据Animationvalue值来设置宽高

  6. widgetdispose()方法中调用释放资源


最终效果如下:



注意:上面创建Tween用了Dart语法的级联符号


animation = tween.animate(controller)..addListener(() {setState(() {// the animation object’s value is the changed state});});


等价于下面代码:


animation = tween.animate(controller);animation.addListener(() {setState(() {// the animation object’s value is the changed state});});


所以还是有必要学一下Dart语法。

1.1.AnimatedWidget 简化

使用AnimatedWidget对动画进行简化,使用AnimatedWidget创建一个可重用动画的widget,而不是用addListener()setState()来给widget添加动画。AnimatedWidget类允许从setState()调用中的动画代码中分离出widget代码。AnimatedWidget不需要维护一个State对象了来保存动画。


import 'package:flutter/material.dart';import 'package:flutter/animation.dart';


void main() {//运行程序 runApp(LogoApp());}


class LogoApp extends StatefulWidget{@overrideState<StatefulWidget> createState(){return new _LogoAppState();}


}


//logoWidget ImageLogo = new Image(image: new AssetImage('images/logo.jpg'),);


//抽象出来 class AnimatedLogo extends AnimatedWidget{AnimatedLogo({Key key,Animation<double> animation}):super(key:key,listenable:animation);


@overrideWidget build(BuildContext context){final Animation<double> animation = listenable;return new MaterialApp(theme: ThemeData(primarySwatch: Colors.red


),home: new Scaffold(appBar: new AppBar(title: Text("动画 demo"),),body:new Center(child: new Container(//宽和高都是根据 animation 的值来变化 height: animation.value,width: animation.value,child: ImageLogo,),),),);


}}


//with 是 dart 的关键字,混入的意思,将一个或者多个类的功能添加到自己的类无需继承这些类//避免多重继承问题//SingleTickerProviderStateMixin 初始化 animation 和 Controller 的时候需要一个 TickerProvider 类型的参数 Vsync//所依混入 TickerProvider 的子类 class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{//动画的状态,如动画开启,停止,前进,后退等 Animation<double> animation;//管理者 animation 对象 AnimationController controller;@overridevoid initState() {// TODO: implement initStatesuper.initState();//创建 AnimationController//需要传递一个 vsync 参数,存在 vsync 时会防止屏幕外动画(//译者语:动画的 UI 不在当前屏幕时)消耗不必要的资源。 通过将 SingleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为 vsync 的值。controller = new AnimationController(//时间是 3000 毫秒 duration: const Duration(milliseconds: 3000),//vsync 在此处忽略不必要的情况 vsync: this,);//补间动画 animation = new Tween(//开始的值是 0begin: 0.0,//结束的值是 200end : 200.0,).animate(controller);//添加监听器//只显示动画一次 controller.forward();}


@overrideWidget build(BuildContext context){return AnimatedLogo(animation: animation);}


@overridevoid dispose() {// TODO: implement disposesuper.dispose();//资源释放 controller.dispose();}


}


可以发现AnimatedWidget中会自动调用addListenersetState()_LogoAppStateAnimation对象传递给基类并用animation.value设置 Image 宽高。

1.2.监视动画

在平时开发,我们知道,很多时候都需要监听动画的状态,好像完成、前进、倒退等。在Flutter中可以通过addStatusListener()来得到这个通知,以下代码添加了动画状态


//补间动画 animation = new Tween(//开始的值是 0begin: 0.0,//结束的值是 200end : 200.0,).animate(controller)//添加动画状态..addStatusListener((state){return print('$state');});//添加监听器


运行代码会输出下面结果:


I/flutter (16745): AnimationStatus.forward //动画开始 Syncing files to device KNT AL10...I/zygote64(16745): Do partial code cache collection, code=30KB, data=25KBI/zygote64(16745): After code cache collection, code=30KB, data=25KBI/zygote64(16745): Increasing code cache capacity to 128KBI/flutter (16745): AnimationStatus.completed//动画完成


下面那就运用addStatusListener()在开始或结束反转动画。那就产生循环效果:


//补间动画 animation = new Tween(//开始的值是 0begin: 0.0,//结束的值是 200end : 200.0,).animate(controller)//添加动画状态..addStatusListener((state){//如果动画完成了 if(state == AnimationStatus.completed){//开始反向这动画 controller.reverse();} else if(state == AnimationStatus.dismissed){//开始向前运行着动画 controller.forward();}


});//添加监听器


效果如下:


1.3.用 AnimatedBuilder 重构

上面的代码存在一个问题:更改动画需要更改显示Imagewidget,更好的解决方案是将职责分离:


  1. 显示图像

  2. 定义Animation对象

  3. 渲染过渡效果 这时候可以借助AnimatedBuilder类完成此分离。AnimatedBuilder是渲染树中的一个独立的类,与AnimatedWidget类似,AnimatedBuilder自动监听来自Animation对象的通知,并根据需要将该控件树标记为脏(dirty),因此不需要手动调用addListener()


//AnimatedBuilderclass GrowTransition extends StatelessWidget{final Widget child;final Animation<double> animation;GrowTransition({this.child,this.animation});


@overrideWidget build(BuildContext context){return new MaterialApp(theme: ThemeData(primarySwatch: Colors.red


),home: new Scaffold(appBar: new AppBar(title: Text("动画 demo"),),body:new Center(child: new AnimatedBuilder(animation: animation,builder: (BuildContext context,Widget child){return new Container(//宽和高都是根据 animation 的值来变化 height: animation.value,width: animation.value,child: child,);},child: child,),


),),);


}class _LogoAppState extends State<LogoApp> with SingleTickerProviderStateMixin{//动画的状态,如动画开启,停止,前进,后退等 Animation animation;//管理者 animation 对象 AnimationController controller;@overridevoid initSta


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


te() {// TODO: implement initStatesuper.initState();//创建 AnimationController//需要传递一个 vsync 参数,存在 vsync 时会防止屏幕外动画(//译者语:动画的 UI 不在当前屏幕时)消耗不必要的资源。 通过将 SingleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为 vsync 的值。controller = new AnimationController(//时间是 3000 毫秒 duration: const Duration(milliseconds: 3000),//vsync 在此处忽略不必要的情况 vsync: this,);final CurvedAnimation curve = new CurvedAnimation(parent: controller, curve: Curves.easeIn);//补间动画 animation = new Tween(//开始的值是 0begin: 0.0,//结束的值是 200end : 200.0,).animate(curve)// //添加动画状态..addStatusListener((state){//如果动画完成了 if(state == AnimationStatus.completed){//开始反向这动画 controller.reverse();} else if(state == AnimationStatus.dismissed){//开始向前运行着动画 controller.forward();}


});//添加监听器//只显示动画一次 controller.forward();}


@overrideWidget build(BuildContext context){//return AnimatedLogo(animation: animation);return new GrowTransition(child:ImageLogo,animation: animation);}


@override

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Flutter学习之布局、交互、动画,字节跳动学习笔记