移動端開發新趨勢Flutter

<簡書 — 劉小壯> https://www.jianshu.com/p/1a90adc09e99
               

介紹

FlutterGoogle開發的新一代跨平臺方案,Flutter能夠實現寫一份代碼同時運行在iOS和Android設備上,而且提供很好的性能體驗。Flutter使用Dart做爲開發語言,這是一門簡潔、強類型的編程語言。Flutter對於iOS和Android設備,提供了兩套視覺庫,能夠針對不一樣的平臺有不一樣的展現效果。前端

Flutter本來是爲了解決Web開發中的一些問題,而開發的一套精簡版Web框架,擁有獨立的渲染引擎和開發語言,但後來逐漸演變爲移動端開發框架。正是因爲Dart當初的定位是爲了替代JS成爲Web框架,因此Dart的語法更接近於JS語法。例如定義對象構建方法,以及實例化對象的方式等。git

Google剛推出Flutter時,其發展很緩慢,終於在18年發佈第一個Bate版以後迎來了爆發性增加,發佈第一個Release版時增加速度更快。能夠從Github上Star數據看出來這個增加的過程。在19年最新的Flutter 1.2版本中,已經開放Web支持的Beta版。github

增加趨勢

目前已經有很多大型項目接入Flutter,阿里的鹹魚、頭條的抖音、騰訊的NOW直播,都將Flutter當作應用程序的開發語言。除此以外,還有一些其餘中小型公司也在作。express

總體架構

Flutter能夠理解爲開發SDK或者工具包,其經過Dart做爲開發語言,而且提供MaterialCupertino兩套視覺控件,視圖或其餘和視圖相關的類,都以Widget的形式表現。Flutter有本身的渲染引擎,並不依賴原平生臺的渲染。Flutter還包含一個用C++實現的Engine,渲染也是包含在其中的。編程

Flutter總體架構

Engine

Flutter是一套全新的跨平臺方案,Flutter並不像React Native那樣,依賴原生應用的渲染,而是本身有本身的渲染引擎,並使用Dart當作Flutter的開發語言。Flutter總體框架分爲兩層,底層是經過C++實現的引擎部分,SkiaFlutter的渲染引擎,負責跨平臺的圖形渲染。Dart做爲Flutter的開發語言,在C++引擎上層是DartFrameworkjson

Flutter不只僅提供了一套視覺庫,在Flutter總體框架中包含各個層級階段的庫。例如實現一個遊戲功能,上面一些遊戲控件能夠用上層視覺庫,底層遊戲能夠直接基於Flutter的底層庫進行開發,而不須要調用原生應用的底層庫。Flutter的底層庫是基於Open GL實現的,因此Open GL能夠作的Flutter均可以。數組

視覺庫

在上層Framework中包含兩套視覺庫,符合Android風格的Material,和符合iOS風格的Cupertino。也能夠在此基礎上,封裝本身風格的系統組件。Cupertino是一套iOS風格的視覺庫,包含iOS的導航欄、buttonalertView等。bash

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 麻煩各位大佬點個贊,謝謝!😁

相關文章
相關標籤/搜索