2024年安卓最全Flutter学习之布局、交互、动画(1),2024年最新今日头条软件测试面试题

最后

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。

还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

Android 基础知识点

Java 基础知识点

Android 源码相关分析

常见的一些原理性问题

希望大家在今年一切顺利,进到自己想进的公司,共勉!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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开始 每调用一次这个函数,迭代器就会加1
itemBuilder: _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: [
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: [
(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,但没有完成onTap
this.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.0
this.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): onTapDown
I/flutter (16304): onTapUp
I/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): 长按onLongPress
I/flutter (16304): 长按抬起onLongPressUP
I/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): onPanDown
I/flutter (16304): onPanStart
I/flutter (16304): onPanUpdate
I/flutter (16304): onPanUpdate
I/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): onScaleStart
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleUpdate
I/flutter (16304): onScaleEnd

3.原始指针事件

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

  1. PointerDownEvent:指针接触到屏幕的特定位置
  2. PointerMoveEvent:指针从屏幕上的一个位置移动到另一个位置
  3. PointMoveEvent:指针停止接触屏幕
  4. PointUpEvent:指针停止接触屏幕
  5. PointerCancelEvent:指针的输入事件不再针对此应用

上代码:

//Pointer
Widget 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): onPointerDown
I/flutter (16304): onPointerMovee
I/flutter (16304): onPointerMove
I/flutter (16304): onPointerMoves
I/flutter (16304): onPointerMove
I/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 {
@override
Widget 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{
@override
State createState(){
return new HomeWidget();
}

}

class HomeWidget extends State {
@override
Widget build(BuildContext context) {

//Pointer
Widget 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{

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

}
}

class HomeWidget extends StatelessWidget{

@override
Widget 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);

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

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

@override
Widget 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{
@override
State createState(){
return new _LogoAppState();
}

}

//logo
Widget ImageLogo = new Image(
image: new AssetImage(‘images/logo.jpg’),
);

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

});
});
//只显示动画一次
controller.forward();
}
@override
Widget 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,
),
),
),
);
}

@override
void dispose() {
// TODO: implement dispose
super.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{
@override
State createState(){
return new _LogoAppState();
}

}

//logo
Widget ImageLogo = new Image(
image: new AssetImage(‘images/logo.jpg’),
);

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

@override
Widget build(BuildContext context){
final Animation 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 with SingleTickerProviderStateMixin{
//动画的状态,如动画开启,停止,前进,后退等
Animation animation;
//管理者animation对象
AnimationController controller;
@override
void initState() {
// TODO: implement initState
super.initState();
//创建AnimationController
//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
controller = new AnimationController(
//时间是3000毫秒
duration: const Duration(
milliseconds: 3000
),
//vsync 在此处忽略不必要的情况
vsync: this,
);
//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 200.0,
).animate(controller);//添加监听器
//只显示动画一次
controller.forward();
}

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

@override
void dispose() {
// TODO: implement dispose
super.dispose();
//资源释放
controller.dispose();
}

}

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

1.2.监视动画

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

//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 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=25KB
I/zygote64(16745): After code cache collection, code=30KB, data=25KB
I/zygote64(16745): Increasing code cache capacity to 128KB
I/flutter (16745): AnimationStatus.completed//动画完成

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

//补间动画
animation = new Tween(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 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()

//AnimatedBuilder
class GrowTransition extends StatelessWidget{
final Widget child;
final Animation animation;
GrowTransition({this.child,this.animation});

@override
Widget 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 with SingleTickerProviderStateMixin{
//动画的状态,如动画开启,停止,前进,后退等
Animation animation;
//管理者animation对象
AnimationController controller;
@override
void initState() {
// TODO: implement initState
super.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(
//开始的值是0
begin: 0.0,
//结束的值是200
end : 200.0,
).animate(curve)
// //添加动画状态
…addStatusListener((state){
//如果动画完成了
if(state == AnimationStatus.completed){
//开始反向这动画
controller.reverse();
} else if(state == AnimationStatus.dismissed){
//开始向前运行着动画
controller.forward();
}

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

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

@override
void dispose() {
// TODO: implement dispose
super.dispose();
//资源释放
controller.dispose();
}

}

上面代码有一个迷惑的问题是,child看起来好像是指定了两次,但实际发生的事情是,将外部引用的child传递给AnimatedBuilderAnimatedBuilder将其传递给匿名构造器,然后将该对象用作其子对象。最终的结果是AnimatedBuilder插入到渲染树中的两个Widget之间。最后,在initState()方法创建一个AnimationController和一个Tween,然后通过animate()绑定,在build方法中,返回带有一个Image为子对象的GrowTransition对象和一个用于驱动过渡的动画对象。如果只是想把可复用的动画定义成一个widget,那就用AnimatedWidget

1.5.并行动画

很多时候,一个动画需要两种或者两种以上的动画,在Flutter也是可以实现的,每一个Tween管理动画的一种效果,如:

final AnimationController controller =
new AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
final Animation sizeAnimation =
new Tween(begin: 0.0, end: 300.0).animate(controller);
final Animation opacityAnimation =
new Tween(begin: 0.1, end: 1.0).animate(controller);

可以通过sizeAnimation.Value来获取大小,通过opacityAnimation.value来获取不透明度,但AnimatedWidget的构造函数只能接受一个动画对象,解决这个问题,需要动画的widget创建了自己的Tween对象,上代码:

//AnimatedBuilder
class GrowTransition extends StatelessWidget {
final Widget child;
final Animation animation;

GrowTransition({this.child, this.animation});
static final _opacityTween = new Tween(begin: 0.1, end: 1.0);
static final _sizeTween = new Tween(begin: 0.0, end: 200.0);

@override
Widget 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 Opacity(
opacity: _opacityTween.evaluate(animation),
child: new Container(
//宽和高都是根据animation的值来变化
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
child: child,
),
);

},
child: child,
),
),
),
);
}
}

class _LogoAppState extends State with SingleTickerProviderStateMixin {
//动画的状态,如动画开启,停止,前进,后退等
Animation animation;

//管理者animation对象
AnimationController controller;

@override
void initState() {
// TODO: implement initState
super.initState();
//创建AnimationController
//需要传递一个vsync参数,存在vsync时会防止屏幕外动画(
//译者语:动画的UI不在当前屏幕时)消耗不必要的资源。 通过将SingleTickerProviderStateMixin添加到类定义中,可以将stateful对象作为vsync的值。
controller = new AnimationController(
//时间是3000毫秒
duration: const Duration(milliseconds: 3000),
//vsync 在此处忽略不必要的情况
vsync: this,
);
//新增
animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn)
…addStatusListener((state) {
//如果动画完成了
if (state == AnimationStatus.completed) {
//开始反向这动画
controller.reverse();
} else if (state == AnimationStatus.dismissed) {
//开始向前运行着动画
controller.forward();
}
}); //添加监听器
//只显示动画一次
controller.forward();
}

@override
Widget build(BuildContext context) {
return new GrowTransition(child:ImageLogo,animation: animation);
}

@override
void dispose() {
// TODO: implement dispose
super.dispose();
//资源释放
controller.dispose();
}
}

可以看到在GrowTransition定义两个Tween动画,并且加了不透明Opacitywidget,最后在initState方法中修改增加一句animation = new CurvedAnimation(parent: controller, curve: Curves.easeIn),最后的动画效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:可以通过改变Curves.easeIn值来实现非线性运动效果。

2.自定义动画

先上效果图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2.1.自定义小球

class _bollView extends CustomPainter{
//颜色
Color color;
//数量
int count;
//集合放动画
List<Animation> ListAnimators;
_bollView({this.color,this.count,this.ListAnimators});
@override
void paint(Canvas canvas,Size size){
//绘制流程
double boll_radius = (size.width - 15) / 8;
Paint paint = new Paint();
paint.color = color;
paint.style = PaintingStyle.fill;
//因为这个wiaget是80 球和球之间相隔5
for(int i = 0; i < count;i++){
double value = ListAnimators[i].value;
//确定圆心 半径 画笔
//第一个球 r
//第二个球 5 + 3r
//第三个球 15 + 5r
//第四个球 30 + 7r
//半径也是随着动画值改变
canvas.drawCircle(new Offset((i+1) * boll_radius + i * boll_radius + i * 5,size.height / 2), boll_radius * (value > 1 ? (2 - value) : value), paint);
}
}

//刷新是否重绘
@override
bool shouldRepaint(CustomPainter oldDelegate){
return oldDelegate != this;

}
}

2.2.配置小球属性

class MyBalls extends StatefulWidget{
Size size;
Color color;
int count;
int seconds;

//默认四个小球 红色
MyBalls({this.size,this.seconds : 400,this.color :Colors.redAccent,this.count : 4});

@override
State createState(){
return MyBallsState();
}

}

2.3.创建动画

//继承TickerProviderStateMixin,提供Ticker对象
class MyBallsState extends State with TickerProviderStateMixin {
//动画集合
List<Animation>animatios = [];
//控制器集合
List animationControllers = [];
//颜色
Animation colors;

@override
void initState(){
super.initState();
for(int i = 0;i < widget.count;i++){
//创建动画控制器
AnimationController animationController = new AnimationController(
vsync: this,
duration: Duration(
milliseconds: widget.count * widget.seconds
));
//添加到控制器集合
animationControllers.add(animationController);
//颜色随机
colors = ColorTween(begin: Colors.red,end:Colors.green).animate(animationController);
//创建动画 每个动画都要绑定控制器
Animation animation = new Tween(begin: 0.1,end:1.9).animate(animationController);
animatios.add(animation);
}
animatios[0].addListener((){
//刷新

最后

目前已经更新的部分资料:



网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

edAccent,this.count : 4});

@override
State createState(){
return MyBallsState();
}

}

2.3.创建动画

//继承TickerProviderStateMixin,提供Ticker对象
class MyBallsState extends State with TickerProviderStateMixin {
//动画集合
List<Animation>animatios = [];
//控制器集合
List animationControllers = [];
//颜色
Animation colors;

@override
void initState(){
super.initState();
for(int i = 0;i < widget.count;i++){
//创建动画控制器
AnimationController animationController = new AnimationController(
vsync: this,
duration: Duration(
milliseconds: widget.count * widget.seconds
));
//添加到控制器集合
animationControllers.add(animationController);
//颜色随机
colors = ColorTween(begin: Colors.red,end:Colors.green).animate(animationController);
//创建动画 每个动画都要绑定控制器
Animation animation = new Tween(begin: 0.1,end:1.9).animate(animationController);
animatios.add(animation);
}
animatios[0].addListener((){
//刷新

最后

目前已经更新的部分资料:

[外链图片转存中…(img-CL37Idbf-1715747011100)]
[外链图片转存中…(img-MXF0TirA-1715747011100)]
[外链图片转存中…(img-73ggggNv-1715747011101)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值