帶你高效入門 Flutter

本文面向 Flutter 初學者,旨在用易懂的方式帶你們入門。除了 Flutter 代碼,還會介紹到語法、原理、特性等基礎知識。相信本文能幫助你學習和理解 Flutter。前端

跨平臺技術簡介

咱們先看一下目前的一些跨平臺方案,從前端渲染的角度來分類的話,大體能夠分爲如下幾種方案。java

WebView 渲染

這種方案就很好理解,如今不少項目都會嵌入 H5 的頁面。就是用 JavaScript 等前端技術進行開發,在客戶端上用 WebView 來進行渲染。微信小程序目前使用的就是這種方案。node

微信小程序

它的優勢很明顯,使用成熟的前端技術進行開發,學習成本低,開發效率高,而且支持動態發佈代碼。android

但缺點也很明顯,在性能體驗上,和原生仍是存在較大差距的。ios

原生控件渲染

既然 WebView 的性能不夠好,因而就有了使用原生控件進行渲染的方案。這種方案,一樣也是使用 JavaScript 開發,區別是它最終是調用原生控件進行渲染的。這種方案的表明是 Facebook 的 React Native。git

React Native

因爲使用原生控件進行渲染,性能體驗也會更接近原生。但也只是更接近,和原生仍是有差距的,由於它須要頻繁的進行 JavaScript 和原生之間的通訊,這個通訊效率是比較低的。github

另外,因爲須要適配各個平臺的控件,那就有可能出現,系統控件更新了,而框架自己尚未更新,由此產生了一些問題。換句話說,這種方案是受到原生控件限制的。npm

繪圖引擎渲染

接下來就是主角了。編程

在前端,若是徹底不使用原生控件,咱們能夠經過系統的繪圖 API 繪製出一個用戶界面。從這個角度出發,能夠在各個平臺使用一個統一接口的繪圖引擎來進行界面繪製,這個引擎最終調用的是系統的 API 繪製的。這樣的話,它的性能能夠作到接近原生,而且又不受原生控件的限制,在不一樣平臺上可以作到 UI 統一。小程序

Flutter 就是這樣的一個開發框架。

Flutter

Flutter 簡介

一個跨平臺 UI 解決方案

Flutter 是由 Google 開發的,一個跨平臺 UI 解決方案。換句話說,它原則上只管 UI 的問題,若是涉及到平臺自己的一些功能,好比調用藍牙、攝像頭,通常仍是須要原生代碼去操做。但如今也會有一些第三方庫幫咱們解決這些問題。

繪圖引擎 Skia

Skia

Flutter 使用 Skia 做爲它的繪圖引擎。Skia 已經被 Google 收購,目前不少 Google 旗下的產品都是用 Skia 繪製的,包括 Android。

Android 內置了 Skia,但 iOS 沒有,因此在打 iOS 安裝包的時候,會把 Skia 一塊兒打進去。這就致使了,用同一份 Flutter 代碼打包以後,iOS 的包要比 Android 的包大一些。

開發語言 Dart

Dart

Flutter 使用的開發語言,叫 Dart。Dart 也是 Google 自家的,它是一門面向對象的語言,從它身上會看到一些其餘開發語言的影子。學習起來難度不大的。

前面講跨平臺方案的時候,能夠發現別的方案基本都是用 JavaScript 做爲開發語言的,但爲何 Flutter 不用?就由於 Dart 是谷歌自家的嗎?這個問題先留着,咱們後面會提到。

搭建開發環境

這裏部分就簡單點帶過了,具體的搭建流程能夠在官網查看:

flutter-io.cn/docs/get-st…

主要的搭建步驟以下:

下載 Flutter SDK

官網下載地址:

flutter.dev/docs/develo…

因爲在國內訪問可能受限,官方爲中國開發者搭建了鏡像:

flutter.dev/community/c…

更新環境變量

解壓後,將 flutter\bin 的全路徑添加到環境變量 PATH 中。

安裝開發工具

理論上,任何文本編輯器均可以用來開發 Flutter 應用,但推薦的開發工具是 Android Studio、IntelliJ 以及 VS Code。由於在這些開發工具上,能夠安裝官方的 Flutter 和 Dart 插件,獲得更好的開發體驗。文章裏使用 Android Studio 來演示。

若是你打算開發 iOS 應用,則還須要安裝 Xcode。

安裝插件

在開發工具的插件設置中,安裝上面說到的 Flutter 和 Dart 插件。Flutter 插件用於支持 Flutter 的運行、調試、熱重載等功能,而 Dart 插件則提供了代碼的輸入校驗、代碼補全等功能。

Hello World!

萬物始於 Hello World,咱們先來建立一個顯示 Hello World 的 Flutter 項目。

在 Android Studio 的歡迎頁面選擇 Start a new Flutter project,或者經過菜單欄的 File > New > New Flutter Project,建立一個新的 Flutter 項目。

建立好的項目裏面包含了 android 和 ios 兩個文件夾,它們是標準的 Android 和 iOS 項目。咱們的 Flutter 代碼,存放在 lib 文件夾裏。項目建立好後,會默認帶一個計數器的示例,咱們無論它,把 main.dart 的代碼改爲 Hello World:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(title: Text("Hello")),
            body: Center(child: Text("Hello World!", style: TextStyle(fontSize: 32)))));
  }
}
複製代碼

啓動一個模擬器,或者連上真機,點擊 Run 運行一下,就能看這樣一個界面了:

Hello World

具體代碼先混個眼熟就好,具體的後面會再講到。

Dart 語法

在寫 Flutter 以前,還要先跟你們簡單介紹一下 Dart 的語法。若是你有 Java 或 JavaScript 的開發經驗,以及面向對象的編程思想,學起來是很快的。

咱們能夠在 test 文件夾下新建一個 dart 文件,用來寫測試代碼。

變量聲明

指定類型

和 java 同樣,咱們能夠這樣來聲明一個整形變量:

int num = 666;
複製代碼

var

同時,咱們也能夠像 JavaScript 同樣,用 var 來聲明:

var num = 666;
複製代碼

但和 JavaScript 不一樣的是,如下代碼在 JavaScript 是不會報錯的,但在 Dart 裏會報錯:

var num = 666;

void main() {
  num = "666"; // 報錯
}
複製代碼

在 Dart 裏用 var 聲明一個變量以後,它會根據第一次賦值,來推斷變量的類型,以後就不能再改變類型了。也就是說,JavaScript 是一種弱類型的語言,而 Dart 是一種強類型的語言。

Object

若是非要上面這樣寫,那也能夠。把 var 換成 Object 就不報錯了:

Object num = 666;

void main() {
  num = "666";
}
複製代碼

和 Java 相似,Object 是全部對象的根基類。可是這樣的話,若是想打印一下 num 的字符串長度,是會報錯的:

Object num = 666;

void main() {
  num = "666";
  print(num.length); // 報錯
}
複製代碼

由於 length 是屬於 String 的,但系統只知道 num 是一個對象,並不知道它是一個 String。

dynamic

若是仍是非要這樣寫,那也能夠。Dart 有一個特有的關鍵字 dynamic,把 Object 改爲 dynamic 就不報錯了:

dynamic num = 666;

void main() {
  num = "666";
  print(num.length);
}
複製代碼

咱們運行一下這個文件,能夠在控制檯看到正確打印出了字符串長度。

dynamic 是動態的意思,使用它聲明的話,系統會去找到可用的屬性和函數來調用。但這種寫法實際上是不太安全的,由於即便你把屬性名拼錯了,編譯器也是不會報錯的,要到運行時纔會報錯。

函數

dynamic

在 Dart 裏,函數也是能夠不寫返回類型的,不寫的話會被當作 dynamic 來處理。這樣的話,函數的類型就是 return 的類型,若是沒有 return 則是 void 類型。好比能夠這樣:

main() {
  print(getNum().length);
}

getNum(){
  return "666";
}
複製代碼

運行以後是能正確打印出字符串長度的。

用於傳參

Dart 裏的函數也是一個對象,因此能夠把函數做爲參數來傳遞,好比:

main() {
  execute(print666);
}

print666() {
  print("666");
}

execute(Function fun){
  fun();
}
複製代碼

可選參數

在 Dart 的函數傳參裏,有一個叫可選參數的概念,咱們以文字控件 Text 爲例,在源碼裏能夠看到 Text 的構造函數是這樣的:

const Text(
    this.data, {
    Key key,
    this.style,
    this.strutStyle,
    this.textAlign,
    this.textDirection,
    this.locale,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
    this.semanticsLabel,
})
複製代碼

首先,在參數裏有一個 data,它是要顯示的文字內容,是一個必填項。而 data 後面的一堆參數,是用一個大括號括起來的,這些參數就叫作可選參數,意思是這些參數可傳可不傳。

假如咱們要顯示一個比較長的文字,又想限制它最多顯示兩行,就能夠這樣來建立一個 Text:

new Text("666666666666", maxLines: 2); 
複製代碼

可選參數,在 Flutter 裏面用的很是多。

異步

Future

在 Dart 裏使用 Future 來處理異步任務,好比咱們如今延時一秒打印 666,代碼以下:

void main() {
  Future.delayed(new Duration(seconds: 1), () {
    return "666";
  }).then((data) {
    // 成功後執行
    print(data);
  }).catchError((e) {
    // 失敗後執行
    print("error");
  }).whenComplete(() {
    // 不管成功失敗都會執行
    print("complete");
  });
}
複製代碼

Future 的語法和 Promise 很是像。任務執行成功會調用 then,執行失敗會調用 catchError,而不管成功仍是失敗,都會調用 whenComplete。

async/await

若是你不喜歡上面那種寫法,或者是想把異步轉成同步,就能夠用 async 和 await 這兩個關鍵字來轉換。

咱們把上面的代碼轉換一下,寫一個 getString 方法,返回的類型是 Future,它會延時返回一個字符串。在 main 函數後面加上 async 關鍵字,在 getString() 前面加上 await,代碼以下:

void main() async{
  print(await getString());
}

getString() {
  return Future.delayed(new Duration(seconds: 1), () {
    return "666";
  });
}
複製代碼

運行以後能夠看到,能正常延時一秒後,把字符串打印出來。這裏 getString() 返回的類型是 Future,而 await getString() 則是返回了延時以後返回的字符串。await 要在 async 的函數裏面才能使用。

async 和 await 實際上是一個語法糖,它最終也是轉換成 Future 調用鏈的形式執行的。

萬物皆控件

接下來回到 Flutter,Flutter 裏最重要的一個概念是 Widget(下面翻譯做控件)。

在原生開發裏面,咱們可能會在界面上區分,這是一個 View,這是一個 Layout,這是一個 View Controller。但在 Flutter 裏面,它們全都屬於一個統一的模型 Widget。能夠說,在 Flutter 界面裏,全部東西都是 Widget。

之前學面向對象的時候,咱們都聽過一句話,叫萬物皆對象。我這裏套用一下,在 Flutter 裏,萬物皆控件

具體有哪些控件,我作了一下簡單的分類。

根控件

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MyWidget();
  }
}
複製代碼

全部的控件都屬於 StatefulWidgetStatelessWidget。它們的區別是,StatefulWidget 擁有狀態 State,而 StatelessWidget 沒有。

StatefulWidget

當一個控件是可變的時候,就要使用 StatefulWidget 來構建。StatefulWidget 自己不可變,但它持有的狀態 State 是可變的。

StatelessWidget

當一個控件狀態是固定不可變的時候,就可使用 StatelessWidget。前面咱們寫的 Hello World 就是使用 StatelessWidget。

容器控件

Center(
    child: MyWidget()
)
複製代碼

容器類控件通常是將某些屬性或配置,做用在它的子控件上,好比控件所在的寬高、背景、位置等。

經常使用的容器控件有 Container、Center、Padding 等。

佈局控件

Column(children: <Widget>[
    MyWidget1(),
    MyWidget2()
])
複製代碼

佈局控件能夠類比做原生開發中的 Layout,一般它會擁有一個 children 的屬性,用於接收一個控件數組,對這些控件進行特定的排版。

經常使用的佈局控件有 Row、Column、Stack、Flex 等。

基礎控件

Text("666")
複製代碼

基礎控件就是經常使用的文字、按鈕、圖片等控件。

經常使用的基礎控件有 Text、TextField、Button、Image 等。

功能控件

Navigator.of(context).push(MaterialPageRoute(builder: (context) {
    return NewPage();
}));
複製代碼

在 Flutter 裏還有一類控件,它們不影響 UI 佈局,但帶有一些特定的功能,好比頁面跳轉、事件監聽、定義主題等。咱們把這一類控件稱做功能控件。

經常使用的功能控件有 Navigator、NotificationListener、Theme 等。

計數器

開始寫 Flutter 代碼了。還記不記得,在 Flutter 項目建立以後,是自帶一個計數器 demo 的,如今咱們用本身的代碼實現一遍。代碼修改爲以下:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: MyPage());
  }
}

class MyPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MyPageState();
  }
}

class MyPageState extends State<MyPage> {
  int num = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("計數器"),
        ),
        body: Center(
            child: Text(
                num.toString(), 
                style: TextStyle(fontSize: 36)
            )
        ),
        floatingActionButton: FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: () {
              setState(() {
                num++;
              });
            }));
  }
}
複製代碼

運行以後,就能夠看到這樣的界面了:

計數器

按鈕每點擊一次,數字就會加一。下面咱們來分析一下這段代碼,看下里面用到的一些 Widget。

StatefulWidget

因爲頁面中的數字是跟隨狀態變化的,因此該頁面改用 StatefulWidget。StatefulWidget 並不會直接返回一個 Widget,而是返回狀態 State,在 State 裏再返回 Widget。

Scaffold

Scaffold 是一個標準的 Material Design 頁面,它包含了標題欄、浮動按鈕、側滑菜單、底部導航欄等配置。咱們這裏用到了標題欄 appBar、頁面內容 body、浮動按鈕 floatingActionButton。

AppBar

AppBar 就是標題欄,經過查看控件的構造方法,咱們能夠知道它可配置的屬性。

AppBar 的可選參數除了標題 title,還能夠配置標題前的內容 leading,右側的操做按鈕 anctions,控件垂直高度 elevation 等。咱們只傳了 title,其餘屬性都用默認值。

Center

Center 是一個容器類控件,它的做用就是讓它的子控件居中顯示。

FloatingActionButton

熟悉安卓開發的應該對這個控件比較熟悉,它就是頁面右下角一個特定樣式的 Button,參數裏面的 onPressed 是一個必填項,要傳一個點擊以後的回調函數。

根據這個例子,下面給你們介紹一下 Flutter 兩個比較重要的特性。

響應式 UI

點擊 Button 以後,咱們把 num 變量加一,並使用 setState 通知狀態發生了改變,Flutter 會根據新的狀態更新 UI。若是有接觸太小程序開發,setState 就和小程序的 setData 相似。

在 Flutter 裏面咱們不須要用 set 方法來更新 UI,可變控件是和狀態綁定的,這就是 Flutter 的響應式 UI 編程。

熱重載

在 Android Q 和 iOS 13 裏都加入了暗黑模式,咱們也換一個暗黑主題來玩一下。MaterialApp 裏有一個 theme 的屬性,咱們把它配置一下:

return MaterialApp(
    theme: ThemeData.dark(), 
    home: MyPage()
);
複製代碼

此次改完以後不點 Run 了,咱們點一下閃電圖標 Flutter Hot Reload,就能看到界面發生了變化:

這就是 Flutter 的熱重載,在修改完代碼以後,經過熱重載就能立刻在設備上看到修改結果,能夠很大程度上增長開發效率。

下面再給你們介紹幾個 Flutter 裏的常見操做。

頁面跳轉

在 Flutter 裏,使用 Navigator 來管理頁面跳轉,好比要跳轉到一個 NewPage 能夠這樣寫:

Navigator.of(context).push(MaterialPageRoute(builder: (context) {
    return NewPage();
}));
複製代碼

進棧使用 push,出棧則是 pop。

使用 MaterialPageRoute 會模擬出 Android 上頁面跳轉的過場效果。

本地圖片

咱們來看看怎麼顯示一張本地圖片。

先在根目錄新建一個存放圖片的文件夾,好比叫 images,把圖片 picture.png 放進去。

找到根目錄下的 pubspec.yaml 文件,這個即是 Flutter 依賴配置文件,咱們須要在這裏配置一下剛纔的圖片:

assets:
    - images/picture.png
複製代碼

這樣,咱們就能使用 Image 控件把這張圖片顯示出來了:

Image.asset("images/picture.png"))
複製代碼

引入第三方庫

和 node 的 npm 以及 Android 的 jcenter 相似,Flutter 也擁有一個公共倉庫 pub.dev。pub.dev 是 Google 官方的 Dart 倉庫,在上面能夠找到咱們須要的包和插件。

Flutter 自己沒有 Toast,咱們來接入一個。在 pub.dev 上搜索後,我決定使用 fluttertoast

按照說明,在 pubspec.yaml 文件裏的 dependencies 下配置:

fluttertoast: ^3.1.0
複製代碼

點一下 Android Studio 右上角的 Packages get 同步以後就可使用了:

Fluttertoast.showToast(msg: "666666");
複製代碼

iOS 樣式

咱們上面使用的都是 Material Design 的控件,它們都是在 flutter/material.dart 包裏面的。若是要使用 iOS 風格的控件,則要用到 flutter/cupertino.dart 包:

import 'package:flutter/cupertino.dart';
複製代碼

iOS 風格的控件,基本都以 Cupertino 開頭。咱們把計時器頁面裏的控件替換一下:

import 'package:flutter/cupertino.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(home: MyPage());
  }
}

class MyPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return MyPageState();
  }
}

class MyPageState extends State<MyPage> {
  int num = 0;

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
        navigationBar: CupertinoNavigationBar(
          middle: Text("計數器"),
        ),
        child: Container(
            width: double.infinity,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Text(num.toString(), style: TextStyle(fontSize: 36)),
                CupertinoButton(
                    color: CupertinoColors.activeBlue,
                    child: Icon(CupertinoIcons.add),
                    onPressed: () {
                      setState(() {
                        num++;
                      });
                    })
              ],
            )));
  }
}
複製代碼

效果以下:

編譯方式

代碼的部分就到這裏了,接下來跟你們聊一下編譯方式,編程語言的編譯方式有兩種。

  • JIT (Just in time) - 邊運行、邊編譯

  • AOT (Ahead of time) - 先編譯,再運行

關於它們孰優孰劣,就要看從哪一個角度去對比了。JIT 的話,它的一大特色就是支持動態發佈代碼,也就是支持熱更新。但要是從性能的角度考慮,AOT 會更好,由於在運行的時候不用再進行編譯的操做的,運行的效率會更高一些。

回到咱們一開始的時候留下的問題,爲何別的跨平臺方案都是用 JavaScript,而 Flutter 要用 Dart 來開發。JavaScript 的編譯方式是 JIT 的,它不支持 AOT。而 Dart 同時支持 JIT 和 AOT。

Flutter 在開發階段使用 JIT,讓咱們用上了熱重載,增長了開發效率。在打包時改用 AOT,保證了正式版應用的性能。

熱更新?

最後講一下你們比較關心的一個東西,Flutter 是否支持熱更新?前面說到 Dart 支持 JIT,因此從技術層面它是支持的。可是目前是不支持的,在官方的計劃文檔中,能夠看到:

至於緣由,官方在這裏進行了說明。總的來講,是因爲政策的限制,以及出於對性能和安全性的考慮,暫時不支持了。

結束

到這就結束啦。因爲想把 Flutter 基礎在一篇內講完,沒有涉及太多細節,若是要寫 Flutter 代碼還須要深刻學習。但相信理解以後再學,會輕鬆不少。

妥妥的。

相關文章
相關標籤/搜索