Flutter入門基礎

官方文檔


預研內容主要分爲幾個部分:

  1. Flutter環境搭建(技術框架, 安裝,編輯器)
  2. Flutter的UI及交互(佈局,交互,手勢,動畫,路由導航)
  3. Flutter資源文件管理(圖片資源,文件資源,文件的讀寫)
  4. Flutter數據存儲
  5. Flutter的網絡請求(網絡HTTP,JSON序列化)
  6. Flutter與原生平臺(平臺特定代碼交互)
  7. Flutter開發語言(Dart語言)
  8. Flutter其他

Flutter環境搭建

  • 技術框架

在Flutter中用Widget來描述界面,Widget只是View的“配置信息”,編寫的時候利用Dart語言一些聲明式特性來得到類似結構化標記語言的可讀性。Widget根據佈局形成一個層次結構。每個widget嵌入其中,並繼承其父項的屬性。沒有單獨的“應用程序”對象,相反,根widget扮演着這個角色。在Flutter中,一切皆爲Widget,甚至包括css樣式。

  • Flutter環境安裝

mac下終端操作:
vim ~/.bash_profile 添加路徑(沒有.bash_profile的時候,需要通過vim命令創建)
export PUB_HOSTED_URL=https://pub.flutter-io.cn //國內用戶需要設置
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn //國內用戶需要設置
export PATH=PATH_TO_FLUTTER_GIT_DIRECTORY/flutter/bin:$PATH
修改 ~/.zshrc ,在其中添加:source ~/.bash_profile (沒有.zshrc的時候,需要通過vim命令創建)

  • 編輯器

Android Studio 安裝

安裝Flutter和Dart插件

在Flutter插件中,可使用以下模板:
前綴stless: 創建一個StatelessWidget的子類.
前綴stful: 創建一個StatefulWidget子類並且關聯到一個State子類.
前綴stanim: 創建一個StatefulWidget子類, 並且它關聯的State子類包括一個 AnimationController

Flutter的UI及交互

  • 佈局

參考:https://flutterchina.club/tutorials/layout/

Text:該 widget 可讓創建一個帶格式的文本。

Row、 Column: 這些具有彈性空間的佈局類Widget可讓您在水平(Row)和垂直(Column)方向上創建靈活的佈局

Stack: 取代線性佈局 (譯者語:和Android中的LinearLayout相似),Stack允許子 widget 堆疊

Container: Container 可讓您創建矩形視覺元素。container 可以裝飾爲一個BoxDecoration

爲了繼承主題數據,widget需要位於MaterialApp內才能正常顯示, 因此我們使用MaterialApp來運行該應用。 //Scaffold是Material中主要的佈局組件.

GestureDetector widget並不具有顯示效果,而是檢測由用戶做出的手勢。 當用戶點擊Container時, GestureDetector會調用它的onTap回調, 在回調中,將消息打印到控制檯。您可以使用GestureDetector來檢測各種輸入手勢,包括點擊、拖動和縮放。

StatefulWidgets是特殊的widget,它知道如何生成State對象,然後用它來保持狀態

在StatefulWidget調用createState之後,框架將新的狀態對象插入樹中,然後調用狀態對象的initState。 子類化State可以重寫initState,以完成僅需要執行一次的工作。 例如,您可以重寫initState以配置動畫或訂閱platform services。initState的實現中需要調用super.initState當一個狀態對象不再需要時,框架調用狀態對象的dispose。 您可以覆蓋該dispose方法來執行清理工作。例如,您可以覆蓋dispose取消定時器或取消訂閱platform services。 dispose典型的實現是直接調用super.dispose。

stateless widget 沒有內部狀態. Icon、 IconButton, 和Text 都是無狀態widget, 他們都是 StatelessWidget的子類。stateful widget 是動態的. 用戶可以和其交互 (例如輸入一個表單、 或者移動一個slider滑塊),或者可以隨時間改變 (也許是數據改變導致的UI更新)

可以使用key來控制框架將在widget重建時與哪些其他widget匹配。默認情況下,框架根據它們的runtimeType和它們的顯示順序來匹配。 使用key時,框架要求兩個widget具有相同的key和runtimeType。

各種widgets的目錄索引,有UI不熟悉的,可以在這裏找到說明:https://flutterchina.club/widgets/
各類Widgets的地址: https://flutterchina.club/widgets/basics/
各種UI佈局需要用到的控件介紹: https://flutterchina.club/widgets/material/
iOS 風格的控件集合 介紹: https://flutterchina.club/widgets/cupertino/

  • UI的一些注意事項

類MyAppBar和MyScaffold中使用了Container、Row、Column、Text、IconButton、Icon、BoxDecoration、Center、Expanded等常用Widget

Theme.of(context)將查找Widget樹並返回樹中最近的Theme。如果我們的Widget之上有一個單獨的Theme定義,則返回該值。如果不是,則返回App主題。 事實上,FloatingActionButton真是通過這種方式找到accentColor的!

ListView的構造函數需要一次創建所有項目,但ListView.builder的構造函數不需要,它將在列表項滾動到屏幕上時創建該列表項。

new ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return new ListTile(
      title: new Text('${items[index]}'),
    );
  },
);

滑動刪除有直接可用的Widget;

將響應轉換爲自定義Dart對象;

class Post {
  final int userId;
  final int id;
  final String title;
  final String body;

  Post({this.userId, this.id, this.title, this.body});

  factory Post.fromJson(Map<String, dynamic> json) {
    return new Post(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}

http package提供了一種方便的方法來爲請求添加headers。您也可以使用dart:iopackage來添加。

Flutter提供各種按鈕和類似的交互式widget。這些widget中的大多數實現了Material Design 指南, 它們定義了一組具有質感的UI組件。可以使用GestureDetector來給任何自定義widget添加交互性。 可以在管理狀態和Flutter Gallery中找到GestureDetector的示例。

如果你要構建一個 CustomButton ,並在構造器中傳入它的 label?那就組合 RaisedButton 和 label,而不是擴展 RaisedButton。

Isolates 是分離的運行線程,並且不和主線程的內存堆共享內存。這意味着你不能訪問主線程中的變量,或者使用 setState() 來更新 UI。正如它們的名字一樣,Isolates 不能共享內存。

在 Flutter 中,最簡單的方法是使用 ListView widget。它表現得既和 iOS 中的 ScrollView 一致,也能和 TableView 一致,因爲你可以給它的 widget 做垂直排布:

  • 交互

參考:https://flutterchina.club/tutorials/interactive/

  • 手勢

要從widget層監聽手勢,使用 GestureDetector.

  • 動畫

參考動畫的說明: https://flutterchina.club/animations/
在 Flutter 中,使用 AnimationController 。這是一個可以暫停、尋找、停止、反轉動畫的 Animation<double> 類型。它需要一個 Ticker 當 vsync 發生時來發送信號,並且在每幀運行時創建一個介於 0 和 1 之間的線性插值(interpolation)。你可以創建一個或多個的 Animation 並附加給一個 controller。

  • 路由

路由的介紹:https://docs.flutter.io/flutter/widgets/Navigator-class.html
在頁面之間跳轉,你有幾個選擇:
具體指定一個由路由名構成的 Map。(MaterialApp
直接跳轉到一個路由。(WidgetApp)
Navigator 類不僅用來處理 Flutter 中的路由,還被用來獲取你剛 push 到棧中的路由返回的結果。通過 await等待路由返回的結果來達到這點。

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

Flutter資源文件管理

  • 圖片資源

iOS 把 images 和 assets 作爲不同的東西,而 Flutter 中只有 assets。被放到 iOS 中 Images.xcasset 文件夾下的資源在 Flutter 中被放到了 assets 文件夾中。assets 可以是任意類型的文件,而不僅僅是圖片。
例如,你可以把 json 文件放置到 my-assets 文件夾中。在 pubspec.yaml 文件中聲明 assets:`assets:

  • my-assets/data.json然後在代碼中使用 AssetBundle 來訪問它:Future<String> loadAsset() async {
    return await rootBundle.loadString('my-assets/data.json');
    }`

images/my_icon.png // Base: 1.0x image
images/2.0x/my_icon.png // 2.0x image
images/3.0x/my_icon.png // 3.0x image

  • 文件的讀寫

PathProvider 插件提供了一種平臺透明的方式來訪問設備文件系統上的常用位置。該類當前支持訪問兩個文件系統位置:

Flutter數據存儲

在 Flutter 中,可以使用 Shared Preferences plugin 來達到相似的功能。它包裹了 UserDefaluts 以及 Android 上等價的 SharedPreferences 的功能。

在 iOS 中,你通過 CoreData 來存儲結構化的數據。這是一個 SQL 數據庫的上層封裝,讓查詢和關聯模型變得更加簡單。在 Flutter 中,使用 SQFlite 插件來實現這個功能。

Flutter的網絡請求

  • 網絡HTTP

使用dio 來發起網絡請求,它是一個強大易用的dart http請求庫,支持Restful API、FormData、攔截器、請求取消、Cookie管理、文件上傳/下載
參見具體說明: https://flutterchina.club/networking/

  • JSON序列化

使用dart:convert庫可以簡單解碼和編碼JSON;要對簡單的JSON進行編碼,請將簡單值(字符串,布爾值或數字字面量)或包含簡單值的Map,list等傳給encode方法:

Flutter與原生平臺

  • 平臺特定代碼交互

交互的方式: https://flutterchina.club/platform-channels/

通過MethodChannel實現。在Flutter當中定義平臺管道,定義平臺需要捕獲的方法名稱->Appdelegate當中註冊定義的管道->管道方法調用的時候,實現平臺方法的調用(達到Flutter調用平臺方法的目的)

  • Flutter頁面路由 與 原生頁面之間跳轉實現:

Flutter 的代碼並不直接在平臺之下運行,相反,Dart 代碼構建的 Flutter 應用在設備上以原生的方式運行,卻“側步躲開了”平臺提供的 SDK。這意味着,例如,你在 Dart 中發起一個網絡請求,它就直接在 Dart 的上下文中運行。你並不會用上平常在 iOS 或 Android 上使用的原生 API。你的 Flutter 程序仍然被原生平臺的 ViewController 管理作一個 view,但是你並不會直接訪問 ViewController 自身,或是原生框架。

我怎麼訪問 GPS 傳感器?使用 location 社區插件。

我怎麼訪問攝像頭?image_picker 在訪問攝像頭時非常常用。

Flutter開發語言

  • Dart語言一些語法特性:

所有沒有初始化的變量值都是 null。
注意: 只有當名字衝突的時候才使用 this。否則的話, Dart 代碼風格樣式推薦忽略 this。
注意: 如果在構造函數的初始化列表中使用 super(),需要把它放到最後。 詳情參考 Dart 最佳實踐。
在構造函數體執行之前除了可以調用超類構造函數之外,還可以 初始化實例參數。 使用逗號分隔初始化表達式。

class Point {
  num x;
  num y;

  Point(this.x, this.y);

  // Initializer list sets instance variables before
  // the constructor body runs.
  Point.fromJson(Map jsonMap)
      : x = jsonMap['x'],
        y = jsonMap['y'] {
    print('In Point.fromJson(): ($x, $y)');
  }
}

有時候一個構造函數會調動類中的其他構造函數。 一個重定向構造函數是沒有代碼的,在構造函數聲明後,使用 冒號調用其他構造函數。 Point.alongXAxis(num x) : this(x, 0);

如果一個構造函數並不總是返回一個新的對象,則使用 factory 來定義 這個構造函數。例如,一個工廠構造函數 可能從緩存中獲取一個實例並返回,或者 返回一個子類型的實例。

class Logger {
  final String name;
  bool mute = false;

  // _cache is library-private, thanks to the _ in front
  // of its name.
  static final Map<String, Logger> _cache =
      <String, Logger>{};

  factory Logger(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final logger = new Logger._internal(name);
      _cache[name] = logger;
      return logger;
    }
  }

  Logger._internal(this.name);

  void log(String msg) {
    if (!mute) {
      print(msg);
    }
  }
}

Abstract methods(抽象函數): 實例函數、 getter、和 setter 函數可以爲抽象函數, 抽象函數是隻定義函數接口但是沒有實現的函數,由子類來 實現該函數。如果用分號來替代函數體則這個函數就是抽象函數。

abstract class Doer {
  // ...Define instance variables and methods...

  void doSomething(); // Define an abstract method.
}

class EffectiveDoer extends Doer {
  void doSomething() {
    // ...Provide an implementation, so the method is not abstract here...
  }
}

操作符可以被覆寫。 例如,如果你定義了一個 Vector 類, 你可以定義一個 + 函數來實現兩個向量相加。

如果你使用 noSuchMethod() 函數來實現每個可能的 getter 、setter、 以及其他類型的函數,你可以使用 @proxy 註解來避免警告信息:

枚舉類型通常稱之爲 enumerations 或者 enums, 是一種特殊的類,用來表現一個固定 數目的常量。枚舉的 values 常量可以返回 所有的枚舉值。

Mixins 是一種在多類繼承中重用 一個類代碼的方法。使用 with 關鍵字後面爲一個或者多個 mixin 名字來使用 mixin

class Musician extends Performer with Musical {
  // ...
}

class Maestro extends Person
    with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}

如果你查看 List 類型的 API 文檔, 則可以看到 實際的類型定義爲 List<E>。 這個 <…> 聲明 list 是一個 泛型 (或者 參數化) 類型。 通常情況下,使用一個字母來代表類型參數, 例如 E, T, S, K, 和 V 等。T 是一個備用類型。這是一個類型佔位符, 在開發者調用該接口的時候會指定具體類型。

如果你導入的兩個庫具有衝突的標識符, 則你可以使用庫的前綴來區分。 例如,如果 library1 和 library2 都有一個名字爲 Element 的類, 你可以這樣使用:

import 'package:lib1/lib1.dart';
import 'package:lib2/lib2.dart' as lib2;
// ...
Element element1 = new Element();           // Uses Element from lib1.
lib2.Element element2 = new lib2.Element(); // Uses Element from lib2.

Dart 有一些語言特性來支持 異步編程。 最常見的特性是 async 方法和 await 表達式。要使用 await,其方法必須帶有 async 關鍵字:

在一個方法上添加 async 關鍵字,則這個方法返回值爲 Future。 例如,下面是一個返回字符串 的同步方法: String lookUpVersionSync() => '1.0.0';如果使用 async 關鍵字,則該方法 返回一個 Future,並且 認爲該函數是一個耗時的操作。Future<String> lookUpVersion() async => '1.0.0';

在 await expression 中, expression 的返回值通常是一個 Future; 如果返回的值不是 Future,則 Dart 會自動把該值放到 Future 中返回。 Future 對象代表返回一個對象的承諾(promise)。 await expression 執行的結果爲這個返回的對象。 await expression 會阻塞住,直到需要的對象返回爲止。如果 await 無法正常使用,確保是在一個 async 方法中。 例如要在 main() 方法中使用 await, 則 main() 方法的函數體必須標記爲 async:

異步 for 循環具有如下的形式:使用 break 或者 return 語句可以 停止接收 stream 的數據, 這樣就跳出了 for 循環並且 從 stream 上取消註冊了。

await for (variable declaration in expression) {
  // Executes each time the stream emits a value.
}

所有的 Dart 代碼在 isolates 中運行而不是線程。 每個 isolate 都有自己的堆內存,並且確保每個 isolate 的狀態都不能被其他 isolate 訪問。

第三方Flutter框架Demo 及文章參考

其他

  • Flutter安裝包大小的問題?

在安卓的安裝包當中會根據工程的不同增加不同的大小。本地打的包和發佈的包大小也可能不一樣。

部分團隊經驗:在Release模式下,安卓端的安裝包大小增加約爲3~4M ,iOS端的安裝包大小增加約爲13~14M,實際增加的大小跟業務端代碼和資源大小有關係;

其他團隊的經驗,引入 Flutter 之前,漲樂財富通的安裝包爲 94MB,引入之後大小爲 100MB,發現增大了 6MB,這其中主要是引入了 Flutter 的 SDK,增加的大小在可以接受的範圍。

參考: Flutter Android/iOS包大小分析

  • Flutter的組件化集成(將Flutter代碼集成進現有的工程)?

對現有工程有侵入。不能完全按照官方的指導來集成。需要把Flutter編譯產物放入主工程。

對混合棧的管理,參考閒魚團隊的開源方案。

參考:使用 Flutter 之後,我們的 CPU 佔用率降了 50%

  • 預研結果
  1. 採用Flutter框架,能夠滿足日常80%以上的基礎UI展示和常見需求。如果涉及複雜動畫或者特定平臺特性的調用,也可以使用Flutter的管道特性,和平臺進行交互實現。

  2. Flutter的性能雖然不如官方宣稱60FPS,但是在流暢性上也接近原生,而且主要能夠在安卓和iOS平臺上實現統一且支持部分定製,儘管安裝包和內存上會有部分提升空間。





開發過程中遇到的實際問題及解決:

  1. 按鈕做倒計時功能
  2. 導航的時候,如果用Navigator.push處理,新的路由頁面如果用MaterailPageRoute創建,那麼 main.dart中 run 的需要是一個MaterailApp
  3. 在編輯的時候,有的時候編輯器輸入顏色的時候,會fetching Doc ,然後AS就會請求文檔地址,網絡阻塞造成卡死。 根據查找的資料,在mac下需要找到 ~/Library/preference/Android studio x.x下面的jav.table.xml這樣的文件,替換裏面JAVDOC節點下的文檔來源地址url,或者有解決方式是直接刪除掉這個文件。 試了一下刪除,效果不是很好,但是有所改善。可以參考這個回答: Android Studio hangs at fetching documentation
  4. ListView自定義顯示Item。 建立數據源 -> 自定義Adapter,處理Item顯示的內容 -> list的內容設置給Adapter -> Adapter設置給ListView;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章