移动端开发新趋势Flutter

该文章属于<简书 — 刘小壮>原创,转载请注明:

<简书 — 刘小壮> https://www.jianshu.com/p/1a90adc09e99前端


封面图

介绍

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

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

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

增加趋势

目前已经有很多大型项目接入Flutter,阿里的咸鱼、头条的抖音、腾讯的NOW直播,都将Flutter当作应用程序的开发语言。除此以外,还有一些其余中小型公司也在作。编程

总体架构

Flutter能够理解为开发SDK或者工具包,其经过Dart做为开发语言,而且提供MaterialCupertino两套视觉控件,视图或其余和视图相关的类,都以Widget的形式表现。Flutter有本身的渲染引擎,并不依赖原平生台的渲染。Flutter还包含一个用C++实现的Engine,渲染也是包含在其中的。json

Flutter总体架构

Engine

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

Flutter不只仅提供了一套视觉库,在Flutter总体框架中包含各个层级阶段的库。例如实现一个游戏功能,上面一些游戏控件能够用上层视觉库,底层游戏能够直接基于Flutter的底层库进行开发,而不须要调用原生应用的底层库。Flutter的底层库是基于Open GL实现的,因此Open GL能够作的Flutter均可以。服务器

视觉库

在上层Framework中包含两套视觉库,符合Android风格的Material,和符合iOS风格的Cupertino。也能够在此基础上,封装本身风格的系统组件。Cupertino是一套iOS风格的视觉库,包含iOS的导航栏、buttonalertView等。网络

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

除了Flutter为咱们作的一些适配外,有一些控件是须要咱们本身作适配的,例如AlertView,在Android和iOS两个平台下的表现就是不一样的。这些iOS特性的控件都定义在Cupertino中,因此建议在进行App开发时,对一些控件进行上层封装。

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

iOS风格

Android风格

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

Dart

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

Dart语言

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

Dart是强类型的,对定义的变量不须要声明其类型,Flutter会对其进行类型推导。若是不想使用类型推导,也能够本身声明指定的类型。

Hot Reload

Flutter支持亚秒级热重载,Android StudioVSCode都支持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方法,这就是热重载的基本过程。

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

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

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

Future<OperationResult> restart({ bool fullRestart = false, bool pauseAfterRestart = false, String reason }) async {
    if (fullRestart) {
        // .....
    } else {
        final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
        final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
        final Status status = logger.startProgress(
            '$progressPrefix hot reload...',
            progressId: 'hot.reload'
        );
        OperationResult result;
        try {
            result = await _reloadSources(pause: pauseAfterRestart, reason: reason);
        } finally {
            status.cancel();
        }
    }
}

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

执行流程

_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. 用于显示的视图,例如ListViewTextContainer等。
  2. 用来操做视图,例如Transform等动画相关。
  3. 视图布局相关,例如CenterExpandedColumn等。

Flutter中,全部的视图都是由Widget组成,LabelAppBarViewController等。在Flutter的设计中,组合的优先级要大于继承,总体视图类结构继承层级很浅但单层不少类。若是想定制或封装一些控件,也应该以组合为主,而不是继承。

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

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

Widget结构

了解Widget

在应用程序启动时,main方法接收一个Widget当作主页面,因此任何一个Widget均可以当作根视图。通常都是传一个MaterialApp,也能够传一个Container当作根视图,这都是被容许的。

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

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

当构建一个Widget时,能够经过其build得到构建流程,在构建流程中能够加入本身的定制操做,例如对其设置title或视图等。

return Scaffold(
  appBar: AppBar(
    title: Text('ListView Demo'),
  ),
  body: ListView.builder(
    itemCount: dataList.length,
    itemBuilder: (BuildContext context, int index) {
      return Text(dataList[index]);
    },
  ),
);

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

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

StatelessWidget

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

class RectangleWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center (
        // UI Code
    );
  }
}

StatefulWidget

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

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

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

class DynamicWidget extends StatefulWidget {
  DynamicWidget({Key key, this.title}) : super (key : key);
  final String title;

  @override
  DynamicWidgetState createState() => new DynamicWidgetState();
}

因为上面动态Widget定义了初始化方法,在调用动态Widget时能够直接用自定义初始化方法便可。

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

State

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

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

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

class DynamicWidgetState extends State<DynamicWidget> {
  int counter = 0;
  void _onPressAction() {
    setState(() {
      counter++;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: Center(
        child: Text('Button tapped $_counter.')
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _onPressAction,
        tooltip: 'Increment',
        child: Icon(Icons.add)
      )
    );
  }  
}

主要Widget

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

在iOS中除了用来显示的视图外,视图还有对应的UIViewController。在Flutter中并无专门用来管理视图而且和View一对一的类,但从显示的角度来讲,有相似的类Scaffold,其包含控制器的appBar,也能够经过body设置一个widget当作其视图。

theme

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

new MaterialApp(
  title: title,
  theme: new ThemeData(
    brightness: Brightness.dark,
    primaryColor: Colors.lightBlue[800],
    accentColor: Colors.cyan[600],
  )
);

若是不想使用系统默认主题,能够将对应的控件或试图用Theme包起来,并将Theme当作Widget赋值给其余Widget

new Theme(
  data: new ThemeData(
    accentColor: Colors.yellow,
  ),
  child: new FloatingActionButton(
    onPressed: () {},
    child: new Icon(Icons.add),
  ),
);

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

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

new Theme(
  data: Theme.of(context).copyWith(accentColor: Colors.yellow),
  child: new FloatingActionButton(
    onPressed: null,
    child: new Icon(Icons.add),
  ),
);

网络请求

Flutter中能够经过asyncawait组合使用,进行网络请求。Flutter中的网络请求大致有三种:

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

http网络库

http网络库定义在http.dart中,内部代码定义很全,包括HttpStatusHttpHeadersCookie等不少基础信息,有助于咱们了解http请求协议。

由于是三方库,因此须要在pubspec.yaml中加入下面的引用。

http: '>=0.11.3+12'

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

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

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

import 'package:http/http.dart' as http;

class RequestDemoState extends State<MyHomePage> {
  List dataList = [];

  @override
  void initState() {
    super.initState();
    loadData();
  }

  // 发起网络请求
  loadData() async{
    String requestURL = 'https://jsonplaceholder.typicode.com/posts';
    Client client = Client();
    Response response = await client.get(requestURL);

    String jsonString = response.body;
    setState(() {
      // 数据解析
      dataList = json.decode(jsonString);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title)
      ),
      body: ListView.builder(
        itemCount: dataList.length,
        itemBuilder: (BuildContext context, int index) {
          return Text(dataList[index]['title']);
        },
      ),
    );
  }
}

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

 void requestData() async {
    var params = Map<String, String>();
    params["username"] = "lxz";
    params["password"] = "123456";

    var client = http.Client();
    var response = await client.post(url_post, body: params);
    _content = response.body;
}

dio网络库

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

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

数据解析

convert

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

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

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

自动序列化

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

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

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

dependencies:
  json_annotation: ^2.0.0

dev_dependencies:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

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

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

import 'package:json_annotation/json_annotation.dart';

// 定义合成后的新文件为user.g.dart
part 'user.g.dart';

@JsonSerializable()

class User {
  String name;
  int age;
  String email;
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

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

part of 'user.dart';

User _$UserFromJson(Map<String, dynamic> json) {
  return User(
      json['name'] as String, json['age'] as int, json['email'] as String);
}

Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
      'name': instance.name,
      'age': instance.age,
      'email': instance.email
    };

有的时候服务端返回的参数名和本地的关键字冲突,或者命名不规范,致使本地定义和服务器字段不一样的状况。这种状况能够经过@JsonKey关键字,来修饰json字段匹配新的本地变量。除此以外,也能够作其余修饰,例如变量不能为空等。

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

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

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文件来管理一些系统依赖库,例如materialcupertinowidgetsanimationgesture等系统库就在里面,这些主要的系统库由.packages下的flutter统一管理,源码都在flutter/lib/scr目录下。除此以外,还有一些其余的系统库或系统资源都在.packages中。

yaml文件

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

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

// 项目配置信息
name: WeChat
description: Tencent WeChat App.
version: 1.0.0+1

// 常规依赖
dependencies:
  flutter:125864
    sdk: flutter
    cupertino_icons: ^0.1.2
    english_words: ^3.1.0

// 开发依赖
dev_dependencies:
  flutter_test:
    sdk: flutter
    
flutter:
  uses-material-design: true
  // 图片依赖
  assets:
    - assets/images/ic_file_transfer.png
    - assets/images/ic_fengchao.png

  // 字体依赖
  fonts:
    - family: appIconFont
      fonts:
        - asset: assets/fonts/iconfont.ttf

Flutter开发

启动函数

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

void main() {
    runApp()
    // code
}

生命周期

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

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

class LifeCycleDemoState extends State<MyHomePage> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    switch (state) {
      case AppLifecycleState.inactive:
        print('Application Lifecycle inactive');
        break;
      case AppLifecycleState.paused:
        print('Application Lifecycle paused');
        break;
      case AppLifecycleState.resumed:
        print('Application Lifecycle resumed');
        break;
      default:
        print('Application Lifecycle other');
    }
  }
}

矩阵变换

Flutter中是支持矩阵变化的,例如rotatescale等方式。Flutter的矩阵变换由Widget完成,须要进行矩阵变换的视图,在外面包一层Transform Widget便可,内部能够设置其变换方式。

child: Container(
    child: Transform(
      child: Container(
        child: Text(
          "Lorem ipsum",
          style: TextStyle(color: Colors.orange[300], fontSize: 12.0),
          textAlign: TextAlign.center,
        ),
        decoration: BoxDecoration(
          color: Colors.red[400],
        ),
        padding: EdgeInsets.all(16.0),
      ),
      alignment: Alignment.center,
      transform: Matrix4.identity()
        ..rotateZ(15 * 3.1415927 / 180),
    ),
  width: 320.0,
  height: 240.0,
  color: Colors.grey[300],
)

Transform中能够经过transform指定其矩阵变换方式,经过alignment指定变换的锚点。

页面导航

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

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

void main() {
  runApp(MaterialApp(
    home: MyAppHome(), // becomes the route named '/'
    routes: <String, WidgetBuilder> {
      '/a': (BuildContext context) => MyPage(title: 'page A'),
      '/b': (BuildContext context) => MyPage(title: 'page B'),
      '/c': (BuildContext context) => MyPage(title: 'page C'),
    },
  ));
}

跳转时经过pushNamed指定map中的key,便可跳转到对应的Widget。若是须要从push出来的页面获取参数,能够经过await修饰push操做,这样便可在新页面pop的时候将参数返回到当前页面。

Navigator.of(context).pushNamed('/b');

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

编码规范

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

  1. 驼峰命名法,方法名、变量名等,都以首字母小写的驼峰命名法。类名也是驼峰命名法,但类名首字母大写。
  2. 文件名,文件命名如下划线进行区分,不使用驼峰命名法。
  3. Flutter中建立Widget对象,能够用new修饰,也能够不用。
child: new Container(
    child: Text(
      'Hello World',
      style: TextStyle(color: Colors.orange, fontSize: 15.0)
    )
)
  1. 函数中能够定义可选参数,以及必要参数。

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

Future<Response> get(url, {Map<String, String> headers});
  1. Dart中在函数定义前加下划线,则表示是私有方法或变量。
  2. Dart经过import引入外部引用,除此以外也能够经过下面的语法单独引入文件中的某部分。
import "dart:collection" show HashMap, IterableBase;

=>调用

Dart中常常能够看到=>的调用方式,这种调用方式相似于一种语法糖,下面是一些经常使用的调用方式。

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

(单一参数) => {函数声明}
elements.map((element) => {
  return element.length;
});

单一参数 => {函数声明}
elements.map(element => {
 return element.length;
});

当只有一个返回值,而且没有逻辑处理时,能够直接省略return,返回数值。

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

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

() => {函数实现}

小技巧

代码重构

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拥有对应的效果。

// 动画效果
floatingActionButton: FloatingActionButton(
    tooltip: 'Fade',
    child: Icon(Icons.brush),
    onPressed: () {
      controller.forward();
    },
),

// 矩阵转换
Transform(
  child: Container(
    child: Text(
      "Lorem ipsum",
      style: TextStyle(color: Colors.orange[300], fontSize: 12.0),
      textAlign: TextAlign.center,
    )
  ),
  alignment: Alignment.center,
  transform: Matrix4.identity()
    ..rotateZ(15 * 3.1415927 / 180),
),

快捷键(VSCode)

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

注意点

  • 使用Flutter要注意代码缩进,若是缩进有问题可能会影响最后的结果,尤为是在.yaml中写配置文件的时候。
  • 由于Flutter是开源的,因此遇到问题后能够进入源码中,找解决方案。
  • 在代码中要注意标点符号的使用,例如第二个建立Stack的代码,若是上面是以逗号结尾,则后面的建立会失败,若是上面是以分号结尾则没问题。
Widget unreadMsgText = Container(
    width: Constants.UnreadMsgNotifyDotSize,
    height: Constants.UnreadMsgNotifyDotSize,
    child: Text(
      conversation.unreadMsgCount.toString(),
      style: TextStyle(
        color: Color(AppColors.UnreadMsgNotifyTextColor),
        fontSize: 12.0
      ),
    ),
  );
  
  avatarContainer = Stack(
    overflow: Overflow.visible,
    children: <Widget>[
      avatar
    ],
  );

简书因为排版的问题,阅读体验并很差,布局、图片显示、代码等不少问题。因此建议到我Github上,下载Flutter编程指南 PDF合集。把全部Flutter文章总计三篇,都写在这个PDF中,并且左侧有目录,方便阅读。

Flutter编程指南

下载地址:Flutter编程指南 PDF麻烦各位大佬点个赞,谢谢!😁

相关文章
相关标签/搜索