热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

flutterscrollview_给我十分钟!带你Flutter从入门到上天!!!

简介Flutter是Google开发的新一代跨平台方案,Flutter可以实现写一份代码同时运行在iOS和Android设备上,并且提供很好的性能体验。
08eb776a139455017c8c24abfbcf44a4.png

简介

Flutter是Google开发的新一代跨平台方案,Flutter可以实现写一份代码同时运行在iOS和Android设备上,并且提供很好的性能体验。Flutter使用Dart作为开发语言,这是一门简洁、强类型的编程语言。Flutter对于iOS和Android设备,提供了两套视觉库,可以针对不同的平台有不同的展示效果。

Flutter原本是为了解决Web开发中的一些问题,而开发的一套精简版Web框架,拥有独立的渲染引擎和开发语言,但后来逐渐演变为移动端开发框架。正是由于Dart当初的定位是为了替代JS成为Web框架,所以Dart的语法更接近于JS语法。例如定义对象构建方法,以及实例化对象的方式等。

在Google刚推出Flutter时,其发展很缓慢,终于在18年发布第一个Beta版之后迎来了爆发性增长,发布第一个Release版时增长速度更快。可以从Github上Star数据看出来这个增长的过程。在19年最新的Flutter 1.2版本中,已经开放Web支持的Beta版。

8eb9d98b3de09e7782cb61e6469149d5.png

目前已经有不少大型项目接入Flutter,阿里的闲鱼、头条的抖音、腾讯的NOW直播,都将Flutter当做应用程序的开发语言。除此之外,还有一些其他中小型公司也在做。

整体架构

Flutter可以理解为开发SDK或者工具包,其通过Dart作为开发语言,并且提供Material和Cupertino两套视觉控件,视图或其他和视图相关的类,都以Widget的形式表现。Flutter有自己的渲染引擎,并不依赖原生平台的渲染。Flutter还包含一个用C++实现的Engine,渲染也是包含在其中的。

e9097dff91ad256e312f0498755f5b84.png

Engine

Flutter是一套全新的跨平台方案,Flutter并不像React Native那样,依赖原生应用的渲染,而是自己有自己的渲染引擎,并使用Dart当做Flutter的开发语言。Flutter整体框架分为两层,底层是通过C++实现的引擎部分,Skia是Flutter的渲染引擎,负责跨平台的图形渲染。Dart作为Flutter的开发语言,在C++引擎上层是Dart的Framework。

Flutter不仅仅提供了一套视觉库,在Flutter整体框架中包含各个层级阶段的库。例如实现一个游戏功能,上面一些游戏控件可以用上层视觉库,底层游戏可以直接基于Flutter的底层库进行开发,而不需要调用原生应用的底层库。Flutter的底层库是基于Open GL实现的,所以Open GL可以做的Flutter都可以。

视觉库

在上层Framework中包含两套视觉库,符合Android风格的Material,和符合iOS风格的Cupertino。也可以在此基础上,封装自己风格的系统组件。Cupertino是一套iOS风格的视觉库,包含了iOS的导航栏、 button 、 alertView等。

Flutter对不同硬件平台有不同的兼容,例如同样的Material代码运行在iOS和Android不同平台上,有一些平台特有的显示和交互,Flutter依然对其进行了区分适配。例如滑动ScrollView时,iOS平台是有回弹效果的,而Android平台则是阻尼效果。例如iOS的导航栏标题是居中的,Android导航栏标题是向左的,等等。这些Flutter都做了区分兼容。

除了Flutter为我们做的一些适配外,有一些控件是需要我们自己做适配的,例如AlertView,在Android和iOS两个平台下的表现就是不同的。这些iOS特性的控件都定义在Cupertino中,所以建议在进行App开发时,对一些控件进行上层封装。

例如AlertView则对其进行一个二次封装,控件内部进行设备判断并选择不同的视觉库,这样可以保证各个平台的效果。

c972eb03bad1d051852d04f6bfcc0580.png
bcd0ac8b314d26515b37a6f519a6a4b0.png

虽然Flutter对于iOS和Android两个平台,开发有cupertino和material两个视觉库,但实际开发过程中的选择,应该使用material当做视觉库。因为Flutter对iOS的支持并不是很好,主要对Android平台支持比较好,material中的UI控件要比cupertino多好几倍。

Dart

Dart是Google在2011年推出的一款应用于Web开发的编程语言,Dart刚推出的时候,定位是替代JS做前端开发,后来逐步扩展到移动端和服务端。

ba8f61cd4f0ba2e47b4df4776e37bf96.png

Dart是Flutter的开发语言,Flutter必须遵循Dart的语言特性。在此基础上,也会有自己的东西,例如Flutter的上层Framework,自己的渲染引擎等。可以说,Dart只是Flutter的一部分。

Dart是强类型的,对定义的变量不需要声明其类型,Flutter会对其进行类型推导。如果不想使用类型推导,也可以自己声明指定的类型。

Hot Reload

Flutter支持亚秒级热重载,Android Studio和VSCode都支持Hot Reload的特性。但需要区分的是,热重载和热更新是不同的两个概念,热重载是在运行调试状态下,将新代码直接更新到执行中的二进制。而热更新是在上线后,通过Runtime或其他方式,改变现有执行逻辑。

AOT、JIT

Flutter支持AOT(Ahead of time)和JIT(Just in time)两种编译模式,JIT模式支持在运行过程中进行Hot Reload。刷新过程是一个增量的过程,由系统对本次和上次的代码做一次snapshot,将新的代码注入到DartVM中进行刷新。但有时会不能进行Hot Reload,此时进行一次全量的Hot Reload即可。

而AOT模式则是在运行前预先编译好,这样在每次运行过程中就不需要进行分析、编译,此模式的运行速度是最快的。Flutter同时采用了两种方案,在开发阶段采用JIT模式进行开发,在release阶段采用AOT模式,将代码打包为二进制进行发布。

在开发原生应用时,每次修改代码后都需要重新编译,并且运行到硬件设备上。由于Flutter支持Hot Reload,可以进行热重载,对项目的开发效率有很大的提升。

由于Flutter实现机制支持JIT的原因,理论上来说是支持热更新以及服务器下发代码的。可以从服务器。但是由于这样会使性能变差,而且还有审核的问题,所以Flutter并没有采用这种方案。

实现原理

Flutter的热重载是基于State的,也就是我们在代码中经常出现的setState方法,通过这个来修改后,会执行相应的build方法,这就是热重载的基本过程。

Flutter的hot reload的实现源码在下面路径中,在此路径中包含run_cold.dart和run_hot.dart两个文件,前者负责冷启动,后者负责热重载。

1 ~/flutter/packages/flutter_tools/lib/src/run_hot.dart

热重载的代码实现在run_hot.dart文件中,有HotRunner来负责具体代码执行。当Flutter进行热重载时,会调用restart函数,函数内部会传入一个fullRestart的bool类型变量。热重载分为全量和非全量,fullRestart参数就是表示是否全量。以非全量热重载为例,函数的fullRestart会传入false,根据传入false参数,下面是部分核心代码。

1 Future restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) async {2 if (fullRestart) {3 // .....4 } else {5 final bool reloadOnTopOfSnapshot = _runningFromSnapshot;6 final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';7 final Status status = logger.startProgress(8 '$progressPrefix hot reload...',9 progressId: 'hot.reload'
10 );
11 OperationResult result;
12 try {
13 result = await _reloadSources(pause: pauseAfterRestart, reason: reason);
14 } finally {
15 status.cancel();
16 }
17 }
18 }

调用restart函数后,内部会调用_reloadSources函数,去执行内部逻辑。下面是大概逻辑执行流程。

e79b9702305cef976e364c6bcf8ef5c4.png

在_reloadSources函数内部,会调用_updateDevFS函数,函数内部会扫描修改的文件,并将文件修改前后进行对比,随后会将被改动的代码生成一个kernel files文件。

随后会通过HTTP Server将生成的kernel files文件发送给Dart VM虚拟机,虚拟机拿到kernel文件后会调用_reloadSources函数进行资源重载,将kernel文件注入正在运行的Dart VM中。当资源重载完成后,会调用RPC接口触发Widgets的重绘。

跨平台方案对比

现在市面上RN、Weex的技术方案基本一样,所以这里就以RN来代表类似的跨平台方案。Flutter是基于GPU进行渲染的,而RN则将渲染交给原生平台,而自己只是负责通过JSCore将视图组织起来,并处理业务逻辑。所以在渲染效果和性能这块,Flutter的性能比RN要强很多。

跨平台方案一般都需要对各个平台进行平台适配,也就是创建各自平台的适配层,RN的平台适配层要比Flutter要大很多。因为从技术实现来说,RN是通过JSCore引擎进行原生代码调用的,和原生代码交互很多,所以需要更多的适配。而Flutter则只需要对各自平台独有的特性进行适配即可,例如调用系统相册、粘贴板等。

Flutter技术实现是基于更底层实现的,对平台依赖度不是很高,相对来说,RN对平台的依赖度是很高的。所以RN未来的技术升级,包括扩展之类的,都会受到很大的限制。而Flutter未来的潜力将会很大,可以做很多技术改进。

Widget

在Flutter中将显示以及和显示相关的部分,都统一定义为widget,下面列举一些widget包含的类型:

  1. 用于显示的视图,例如ListView、Text、Container等。
  2. 用来操作视图,例如Transform等动画相关。
  3. 视图布局相关,例如Center、Expanded、Column等。

在Flutter中,所有的视图都是由Widget组成,Label、AppBar、ViewController等。在Flutter的设计中,组合的优先级要大于继承,整体视图类结构继承层级很浅但单层很多类。如果想定制或封装一些控件,也应该以组合为主,而不是继承。

在iOS开发中,我也经常采用这种设计方案,组合大于继承。因为如果继承层级过多的话,一个是不便于阅读代码,还有就是不好维护代码。例如底层需要改一个通用的样式,但这个类的继承层级比较复杂,这样改动的话影响范围就比较大,会将一些不需要改的也改掉,这时候就会发现继承很鸡肋。但在iOS中有Category的概念,这也是一种组合的方式,可以通过将一些公共的东西放在Category中,使继承的方便性和组合的灵活性达到一个平衡。

Flutter中并没有单独的布局文件,例如iOS的XIB这种,代码都在Widget中定义。和UIView的区别在于,Widget只是负责描述视图,并不参与视图的渲染。UIView也是负责描述视图,而UIView的layer则负责渲染操作,这是二者的区别。

8a3583aa688116532506ef6ed1d340d5.png

了解Widget

在应用程序启动时,main方法接收一个Widget当做主页面,所以任何一个Widget都可以当做根视图。一般都是传一个MaterialApp,也可以传一个Container当做根视图,这都是被允许的。

在Flutter应用中,和界面显示及用户交互的对象都是由Widget构成的,例如视图、动画、手势等。Widget分为StatelessWidget和StatefulWidget两种,分别是无状态和有状态的Widget。

StatefulWidget本质上也是无状态的,其通过State来处理Widget的状态,以达到有状态,State出现在整个StatefulWidget的生命周期中。

当构建一个Widget时,可以通过其build获得构建流程,在构建流程中可以加入自己的定制操作,例如对其设置title或视图等。

1 return Scaffold(2 appBar: AppBar(3 title: Text('ListView Demo'),4 ),5 body: ListView.builder(6 itemCount: dataList.length,7 itemBuilder: (BuildContext context, int index) {8 return Text(dataList[index]);9 },
10 ),
11 );

有些Widget在构建时,也提供一些参数来帮助构建,例如构建一个ListView时,会将index返回给build方法,来区别构建的Cell,以及构建的上下文context。

1 itemBuilder: (BuildContext context, int index) {
2 return Text(dataList[index]);
3 }

StatelessWidget

StatelessWidget是一种静态Widget,即创建后自身就不能再进行改变。在创建一个StatelessWidget后,需要重写build函数。每个静态Widget都会有一个build函数,在创建视图对象时会调用此方法。同样的,此函数也接收一个Widget类型的返回值。

1 class RectangleWidget extends StatelessWidget {
2 @override
3 Widget build(BuildContext context) {
4 return Center (
5 // UI Code
6 );
7 }
8 }

StatefulWidget

Widget本质上是不可被改变的,但StatefulWidget将状态拆分到State中去管理,当数据发生改变时由State去处理视图的改变。

下面是创建一个动态Widget,当创建一个动态Widget需要配合一个State,并且需要重写createState方法。重写此函数后,指定一个Widget对应的State并初始化。

下面例子中,在StatefulWidget的父类中包含一个Key类型的key变量,这是无论静态Widget还是动态Widget都具备的参数。在动态Widget中定义了自己的成员变量title,并在自定义的初始化方法中传入,通过下面DynamicWidget类的构造方法,并不需要在内部手动进行title的赋值,title即为传入的值,是由系统完成的。

1 class DynamicWidget extends StatefulWidget {
2 DynamicWidget({Key key, this.title}) : super (key : key);
3 final String title;
4
5 @override
6 DynamicWidgetState createState() => new DynamicWidgetState();
7 }

由于上面动态Widget定义了初始化方法,在调用动态Widget时可以直接用自定义初始化方法即可。

1 DynamicWidget(key: 'key', title: 'title');

State

StatefulWidget的改变是由State来完成的,State中需要重写build方法,在build中进行视图组织。StatefulWidget是一种响应式视图改变的方式,数据源和视图产生绑定关系,由数据源驱动视图的改变。

改变StatefulWidget的数据源时,需要调用setState方法,并将数据源改变的操作写在里面。使用动态Widget后,是不需要我们手动去刷新视图的。系统在setState方法调用后,会重新调用对应Widget的build方法,重新绘制某个Widget。

下面的代码示例中添加了一个float按钮,并给按钮设置了一个回调函数_onPressAction,这样在每次触发按钮事件时都会调用此函数。counter是一个整型变量并和Text相关联,当counter的值在setState方法中改变时,Text Widget也会跟着变化。

1 class DynamicWidgetState extends State {2 int counter = 0;3 void _onPressAction() {4 setState(() {5 counter++;6 });7 }89 @override
10 Widget build(BuildContext context) {
11 return new Scaffold(
12 body: Center(
13 child: Text('Button tapped $_counter.')
14 ),
15 floatingActionButton: FloatingActionButton(
16 onPressed: _onPressAction,
17 tooltip: 'Increment',
18 child: Icon(Icons.add)
19 )
20 );
21 }
22 }

主要Widget

在iOS中有UINavigationController的概念,其并不负责显示,而是负责控制各个页面的跳转操作。在Flutter中可以将MaterialApp理解为iOS的导航控制器,其包含一个navigationBar以及导航栈,这和iOS是一样的。

在iOS中除了用来显示的视图外,视图还有对应的UIViewController。在Flutter中并没有专门用来管理视图并且和View一对一的类,但从显示的角度来说,有类似的类Scaffold,其包含控制器的appBar,也可以通过body设置一个widget当做其视图。

theme

theme是Flutter提供的界面风格API,MaterialApp提供有theme属性,可以在MaterialApp中设置全局样式,这样可以统一整个应用的风格。

1 new MaterialApp(
2 title: title,
3 theme: new ThemeData(
4 brightness: Brightness.dark,
5 primaryColor: Colors.lightBlue[800],
6 accentColor: Colors.cyan[600],
7 )
8 );

如果不想使用系统默认主题,可以将对应的控件或试图用Theme包起来,并将Theme当做Widget赋值给其他Widget。

1 new Theme(
2 data: new ThemeData(
3 accentColor: Colors.yellow,
4 ),
5 child: new FloatingActionButton(
6 onPressed: () {},
7 child: new Icon(Icons.add),
8 ),
9 );

有时MaterialApp设定的统一风格,并不能满足某个Widget的要求,可能还需要有其他的外观变化,可以通过Theme.of传入当前的BuildContext,来对theme进行扩展。

Flutter会根据传入的context,顺着Widget树查找最近的Theme,并对Theme复制一份防止影响原有的Theme,并对其进行扩展。

1 new Theme(
2 data: Theme.of(context).copyWith(accentColor: Colors.yellow),
3 child: new FloatingActionButton(
4 onPressed: null,
5 child: new Icon(Icons.add),
6 ),
7 );

网络请求

Flutter中可以通过async、await组合使用,进行网络请求。Flutter中的网络请求大体有三种:

  1. 系统自带的HttpClient网络请求,缺点是代码量相对而言比较多,而且对post请求支持不是很好。
  2. 三方库http.dart,请求简单。
  3. 三方库dio,请求简单。

http网络库

http网络库定义在http.dart中,内部代码定义非常全面,其中包括HttpStatus、HttpHeaders、COOKIE等很多基础信息,有助于我们了解http请求协议。

因为是三方库,所以需要在pubspec.yaml中加入下面的引用。

1 http: '>=0.11.3+12'

下面是http.dart的请求示例代码,可以看到请求很简单,真正的请求代码其实就两行。生成一个Client请求对象,调用client实例的get方法(如果是post则调用post方法),并用Response对象去接收请求结果即可。

通过async修饰发起请求的方法,表示这是一个异步操作,并在请求代码的前面加入await,修饰这里的代码需要等待数据返回,需要过一段时间后再处理。

请求回来的数据默认是json字符串,需要对其进行decode并解析为数据对象才可以使用,这里使用系统自带的convert库进行解析,并解析为数组。

1 import 'package:http/http.dart' as http;23 class RequestDemoState extends State {4 List dataList = [];56 @override7 void initState() {8 super.initState();9 loadData();
10 }
11
12 // 发起网络请求
13 loadData() async{
14 String requestURL = 'https://jsonplaceholder.typicode.com/posts';
15 Client client = Client();
16 Response response = await client.get(requestURL);
17
18 String jsonString = response.body;
19 setState(() {
20 // 数据解析
21 dataList = json.decode(jsonString);
22 });
23 }
24
25 @override
26 Widget build(BuildContext context) {
27 return Scaffold(
28 appBar: AppBar(
29 title: Text(widget.title)
30 ),
31 body: ListView.builder(
32 itemCount: dataList.length,
33 itemBuilder: (BuildContext context, int index) {
34 return Text(dataList[index]['title']);
35 },
36 ),
37 );
38 }
39 }

在调用Client进行post数据请求时,需要传入一个字典进去,Client会通过将字典当做post的from表单。

1 void requestData() async {
2 var params = Map();
3 params["username"] = "lxz";
4 params["password"] = "123456";
5
6 var client = http.Client();
7 var response = await client.post(url_post, body: params);
8 _content = response.body;
9 }

dio网络库

dio库的调用方式和http库类似,这里不过多介绍。dio库相对于http库强大的在于,dio库提供了更好的COOKIE管理、文件的上传下载、fromData表单等处理。所以,如果对网络库需求比较复杂的话,还是建议使用dio。

1 // 引入外部依赖
2 dio: ^1.0.9

数据解析

convert

系统自带有convert解析库,在使用时直接import即可。convert类似于iOS自带的JSON解析类NSJSONSerialization,可以直接将json字符串解析为字典或数组。

1 import 'dart:convert';
2 // 解析代码
3 dataList = json.decode(jsonString);

但是,我们在项目中使用时,一般都不会直接使用字典取值,这是一种很不好的做法。一般都会将字典或数组转换为模型对象,在项目中使用模型对象。可以定义类似Model.dart
这样的模型类,并在模型类中进行数据解析,对外直接暴露公共变量来让外界获取值。

自动序列化

但如果定义模型类的话,一个是要在代码内部写取值和赋值代码,这些都需要手动完成。另外如果当服务端字段发生改变后,客户端也需要跟着进行改变,所以这种方式并不是很灵活。

可以采用json序列化的三方库json_serializable,此库可以将一个类标示为自动JSON序列化的类,并对类提供JSON和对象相互转换的能力。也可以通过命令行开启一个watch,当类中的变量定义发生改变时,相关代码自动发生改变。

首先引入下面的三个库,其中包括依赖库一个,以及调试库两个。

1 dependencies:
2 json_annotation: ^2.0.0
3
4 dev_dependencies:
5 build_runner: ^1.0.0
6 json_serializable: ^2.0.0

定义一个模型文件,例如这里叫做User.dart文件,并在内部定义一个User的模型类。随后引入json_annotation的依赖,通过@JsonSerializable()标示此类需要被json_serializable进行合成。

定义的User类包含两部分,实例变量和两个转换函数。在下面定义json转换函数时,需要注意函数命名一定要按照下面格式命名,否则不能正常生成user.g.dart文件。

1 import 'package:json_annotation/json_annotation.dart';23 // 定义合成后的新文件为user.g.dart4 part 'user.g.dart';56 @JsonSerializable()78 class User {9 String name;
10 int age;
11 String email;
12
13 factory User.fromJson(Map json) => _$UserFromJson(json);
14 Map toJson() => _$UserToJson(this);
15 }

下面就是user.dart指定生成的user.g.dart文件,其中包含JSON和对象相互转换的代码。

1 part of 'user.dart';23 User _$UserFromJson(Map json) {4 return User(5 json['name'] as String, json['age'] as int, json['email'] as String);6 }78 Map _$UserToJson(User instance) => {9 'name': instance.name,
10 'age': instance.age,
11 'email': instance.email
12 };

有的时候服务端返回的参数名和本地的关键字冲突,或者命名不规范,导致本地定义和服务器字段不同的情况。这种情况可以通过@JsonKey关键字,来修饰json字段匹配新的本地变量。除此之外,也可以做其他修饰,例如变量不能为空等。

1 @JsonKey(name: 'id')
2 final int user_id;

现在项目中依然是报错的,随后我们在flutter工程的根目录文件夹下,运行下面命令。

1 flutter packages pub run build_runner watch

此命令的好处在于,其会在后台监听模型类的定义,当模型类定义发生改变后,会自动修改本地源码以适配新的定义。以文中User类为例,当User.dart文件发生改变后,使用Cmd+s保存文件,随后VSCode会将自定改变user.g.dart文件的定义,以适配新的变量定义。

系统文件

主要文件

  • iOS文件:iOS工程文件
  • Android:Android工程文件
  • lib:Flutter的dart代码
  • assets:资源文件夹,例如font、image等都可以放在里面
  • .gitignore:git忽略文件

packages

这是一个系统文件,Flutter通过.packages文件来管理一些系统依赖库,例如material、cupertino、widgets、animation、gesture等系统库就在里面,这些主要的系统库由.packages下的flutter统一管理,源码都在flutter/lib/scr目录下。除此之外,还有一些其他的系统库或系统资源都在.packages中。

yaml文件

在Flutter中通过pubspec.yaml文件来管理外部引用,包含本地资源文件、字体文件、依赖库等依赖,以及应用的一些配置信息。这些配置在项目中时,需要注意代码对其的问题,否则会导致加载失败。

当修改yaml文件的依赖信息后,需要执行flutter get packages命令更新本地文件。但并不需要这么麻烦,可以直接Cmd+s保存文件,VSCode编译器会自动更新依赖。

1 // 项目配置信息2 name: WeChat3 description: Tencent WeChat App.4 version: 1.0.0+156 // 常规依赖7 dependencies:8 flutter:1258649 sdk: flutter
10 cupertino_icons: ^0.1.2
11 english_words: ^3.1.0
12
13 // 开发依赖
14 dev_dependencies:
15 flutter_test:
16 sdk: flutter
17
18 flutter:
19 uses-material-design: true
20 // 图片依赖
21 assets:
22 - assets/images/ic_file_transfer.png
23 - assets/images/ic_fengchao.png
24
25 // 字体依赖
26 fonts:
27 - family: appIconFont
28 fonts:
29 - asset: assets/fonts/iconfont.ttf

Flutter开发

启动函数

和大多数编程语言一样,dart也包含一个main方法,是Flutter程序执行的主入口,在main方法中写的代码就是在程序启动时执行的代码。main方法中会执行runApp方法,runApp方法类似于iOS的UIApplicationMain方法,runApp函数接收一个Widget用来做应用程序的显示。

1 void main() {
2 runApp()
3 // code
4 }

生命周期

在iOS中通过AppDelegate可以获取应用程序的生命周期回调,在Flutter中也可以获取到。可以通过向Binding添加一个Observer,并实现didChangeAppLifecycleState方法,来监听指定事件的到来。

但是由于Flutter提供的状态有限,在iOS平台只能监听三种状态,下面是示例代码。

1 class LifeCycleDemoState extends State with WidgetsBindingObserver {2 @override3 void initState() {4 super.initState();5 WidgetsBinding.instance.addObserver(this);6 }78 @override9 void didChangeAppLifecycleState(AppLifecycleState state) {
10 super.didChangeAppLifecycleState(state);
11
12 switch (state) {
13 case AppLifecycleState.inactive:
14 print('Application Lifecycle inactive');
15 break;
16 case AppLifecycleState.paused:
17 print('Application Lifecycle paused');
18 break;
19 case AppLifecycleState.resumed:
20 print('Application Lifecycle resumed');
21 break;
22 default:
23 print('Application Lifecycle other');
24 }
25 }
26 }

矩阵变换

在Flutter中是支持矩阵变化的,例如rotate、scale等方式。Flutter的矩阵变换由Widget完成,需要进行矩阵变换的视图,在外面包一层Transform Widget即可,内部可以设置其变换方式。

1 child: Container(2 child: Transform(3 child: Container(4 child: Text(5 "Lorem ipsum",6 style: TextStyle(color: Colors.orange[300], fontSize: 12.0),7 textAlign: TextAlign.center,8 ),9 decoration: BoxDecoration(
10 color: Colors.red[400],
11 ),
12 padding: EdgeInsets.all(16.0),
13 ),
14 alignment: Alignment.center,
15 transform: Matrix4.identity()
16 ..rotateZ(15 * 3.1415927 / 180),
17 ),
18 width: 320.0,
19 height: 240.0,
20 color: Colors.grey[300],
21 )

在Transform中可以通过transform指定其矩阵变换方式,通过alignment指定变换的锚点。

页面导航

在iOS中可以通过UINavigationController对页面进行管理,控制页面间的push、pop跳转。Flutter中使用Navigator和Routers来实现类似UINavigationController的功能,Navigator负责管理导航栈,包含push、pop的操作,可以把UIViewController看做一个Routers,Routers被Navigator管理着。

Navigator的跳转方式分为两种,一种是直接跳转到某个Widget页面,另一种是为MaterialApp构建一个map,通过key来跳转对应的Widget页面。map的格式是key : context的形式。

1 void main() {2 runApp(MaterialApp(3 home: MyAppHome(), // becomes the route named '/'4 routes: {5 '/a': (BuildContext context) => MyPage(title: 'page A'),6 '/b': (BuildContext context) => MyPage(title: 'page B'),7 '/c': (BuildContext context) => MyPage(title: 'page C'),8 },9 ));
10 }

跳转时通过pushNamed指定map中的key,即可跳转到对应的Widget。如果需要从push出来的页面获取参数,可以通过await修饰push操作,这样即可在新页面pop的时候将参数返回到当前页面。

1 Navigator.of(context).pushNamed('/b');
2
3 Map coordinates = await Navigator.of(context).pushNamed('/location');
4 Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

编码规范

VSCode有很好的语法检查,如果有命名不规范等问题,都会以警告的形式表现出来。

  • 驼峰命名法,方法名、变量名等,都以首字母小写的驼峰命名法。类名也是驼峰命名法,但类名首字母大写。
  • 文件名,文件命名以下划线进行区分,不使用驼峰命名法。
  • Flutter中创建Widget对象,可以用new修饰,也可以不用。

1 child: new Container(
2 child: Text(
3 'Hello World',
4 style: TextStyle(color: Colors.orange, fontSize: 15.0)
5 )
6 )

  • 函数中可以定义可选参数,以及必要参数。
  • 下面是一个函数定义,这里定义了一个必要参数url,以及一个Map类型的可选参数headers。

1 Future get(url, {Map headers});

  • Dart中在函数定义前加下划线,则表示是私有方法或变量。
  • Dart通过import引入外部引用,除此之外也可以通过下面的语法单独引入文件中的某部分。

1 import "dart:collection" show HashMap, IterableBase;

=>调用

在Dart中经常可以看到=>的调用方式,这种调用方式类似于一种语法糖,下面是一些常用的调用方式。

当进行函数调用时,可以将普通函数调用转换为=>的调用方式,例如下面第一个示例。在此基础上,如果调用函数只有一个参数,可以将其改为第二个示例的方式,也就是可以省略调用的括号,直接写参数名。

1 (单一参数) => {函数声明}
2 elements.map((element) => {
3 return element.length;
4 });
5
6 单一参数 => {函数声明}
7 elements.map(element => {
8 return element.length;
9 });

当只有一个返回值,并且没有逻辑处理时,可以直接省略return,返回数值。

1 (参数1, 参数2, …, 参数N) => 表达式
2 elements.map(element => element.length);

当调用的函数中没有参数时,可以直接省略参数,写一对空括号即可。

1 () => {函数实现}

小技巧

代码重构

VSCode支持对Dart语言进行重构,一般作用范围都是在函数内小范围的。

例如在创建Widget对象的地方,将鼠标焦点放在这里,当前行的最前面会有提示。点击提示后会有下面两个选项:

  • Extract Local Variable将当前Widget及其子Widget创建的代码,剥离到一个变量中,并在当前位置使用这个变量。
  • Extract Method将当前Widget及其子Widget创建的代码,封装到一个函数中,并在当前位置调用此函数。

除此之外,将鼠标焦点放在方法的一行,点击最前面的提示,会出现下面两个选项:

  • Convert to expression body将当前函数转换为一个表达式。
  • Convert to async function body将当前函数转换为一个异步线程中执行的代码。

附加效果

在Dart中添加任何附加效果,例如动画效果或矩阵转换,除了直接给Widget子类的属性赋值外,就是在被当前Widget外面包一层,就可以使当前Widget拥有对应的效果。

1 // 动画效果2 floatingActionButton: FloatingActionButton(3 tooltip: 'Fade',4 child: Icon(Icons.brush),5 onPressed: () {6 controller.forward();7 },8),9
10 // 矩阵转换
11 Transform(
12 child: Container(
13 child: Text(
14 "Lorem ipsum",
15 style: TextStyle(color: Colors.orange[300], fontSize: 12.0),
16 textAlign: TextAlign.center,
17 )
18 ),
19 alignment: Alignment.center,
20 transform: Matrix4.identity()
21 ..rotateZ(15 * 3.1415927 / 180),
22 ),

快捷键(VSCode)

  • Cmd + Shift + p:可以进行快速搜索。需要注意的是,默认是带有一个>的,这样搜索结果主要是dart代码。如果想搜索其他配置文件,或者安装插件等操作,需要把>去掉。
  • Cmd + Shift + o:可以在某个文件中搜索某个类,但前提是需要提前进入这个文件。例如进入framework.dart,搜索StatefulWidget类。

注意点

  • 使用Flutter要注意代码缩进,如果缩进有问题可能会影响最后的结果,尤其是在.yaml中写配置文件的时候。
  • 因为 Flutter是开源的,所以遇到问题后可以进入源码中,找解决方案。
  • 在代码中要注意标点符号的使用,例如第二个创建Stack的代码,如果上面是以逗号结尾,则后面的创建会失败,如果上面是以分号结尾则没问题。

1 Widget unreadMsgText = Container(2 width: Constants.UnreadMsgNotifyDotSize,3 height: Constants.UnreadMsgNotifyDotSize,4 child: Text(5 conversation.unreadMsgCount.toString(),6 style: TextStyle(7 color: Color(AppColors.UnreadMsgNotifyTextColor),8 fontSize: 12.09 ),
10 ),
11 );
12
13 avatarContainer = Stack(
14 overflow: Overflow.visible,
15 children: [
16 avatar
17 ],
18 );

最后

如果你也想进入BATJ一线互联网大厂,如果你也想升职加薪,走上人生巅峰迎娶白富美。那么快来关注我领取免费资料吧

在这里你可以找到一群志同道合的开发者,有招聘都会内推

在这里你可以找到一群大佬交流学习,查漏补缺共同进步

在这里你可以找到免费的学习资料,避免走坑

领取方式:(可以选择自己感兴趣的四个免费领取)

Android架构进阶学习资源资料免费获取

c6e914c0e6beb53c786733b75b238cfd.png

Android架构进阶学习资源资料免费获取



推荐阅读
  • 浏览器作为我们日常不可或缺的软件工具,其背后的运作机制却鲜为人知。本文将深入探讨浏览器内核及其版本的演变历程,帮助读者更好地理解这一关键技术组件,揭示其内部运作的奥秘。 ... [详细]
  • 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
    秒建一个后台管理系统?用这5个开源免费的Java项目就够了 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 怎么入门Android?Android免打包多渠道统计如何实现?含泪整理面经
    热修复技术是Android开发中比较高级的知识点,是中级开发人员通向高级开发中必须掌握的技能。本篇重点讲解热修复热修复的原理,各大热修复框架的比较&#x ... [详细]
  • Android工程师最容易遇到4个瓶颈是什么?附带学习经验
    一些感悟穷人的一次失败,为了还债可能一辈子都翻不了身,为还债一辈子送外卖。你将不再会有精力去思考和投机。穷人的失败可能断送了他所有暴富的机遇和时间&# ... [详细]
  • 从0到1搭建大数据平台
    从0到1搭建大数据平台 ... [详细]
  • 在C#编程中,数值结果的格式化展示是提高代码可读性和用户体验的重要手段。本文探讨了多种格式化方法和技巧,如使用格式说明符、自定义格式字符串等,以实现对数值结果的精确控制。通过实例演示,展示了如何灵活运用这些技术来满足不同的展示需求。 ... [详细]
  • 本文详细介绍了一种利用 ESP8266 01S 模块构建 Web 服务器的成功实践方案。通过具体的代码示例和详细的步骤说明,帮助读者快速掌握该模块的使用方法。在疫情期间,作者重新审视并研究了这一未被充分利用的模块,最终成功实现了 Web 服务器的功能。本文不仅提供了完整的代码实现,还涵盖了调试过程中遇到的常见问题及其解决方法,为初学者提供了宝贵的参考。 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 本文详细介绍了在 Vue.js 前端框架中集成 vue-i18n 插件以实现多语言支持的方法。通过具体的配置步骤和示例代码,帮助开发者快速掌握如何在项目中实现国际化功能,提升用户体验。同时,文章还探讨了常见的多语言切换问题及解决方案,为开发人员提供了实用的参考。 ... [详细]
  • 看官_在GitHub Actions上进行Flutter 的测试和部署
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了在GitHubActions上进行Flutter的测试和部署相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 我正在尝试在网络上运行我的第一个Flutter代码。我按照 ... [详细]
  • 基于Web的Kafka管理工具Kafkamanager首次访问Web界面的详细配置指南(附图解)
    首次访问Kafkamanager Web界面时,需要对Kafka集群进行配置。这一过程相对简单,用户只需依次点击【Cluster】>【Add Cluster】,按照提示完成相关设置即可。本文将通过图文并茂的方式,详细介绍每一步的配置步骤,帮助用户快速上手Kafkamanager。 ... [详细]
author-avatar
awdewqd65_988
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有