Flutter初識

這個春節都關在家裏無事可做,於是想到要去了解下Flutter這個移動SDK。經過幾天瞭解,對Flutter有個大概的認知。
下面我將從以下幾個方面跟大家分享下:
以下這些問題都能在Flutter中文網中獲取,我只是單獨拎了些個人覺得比較能全局性地看Flutter存在的意義的幾個問題。
大家可以自行去看:
傳送門:https://flutterchina.club/faq/

一、什麼是Flutter?
Flutter是一款移動應用程序SDK,一份代碼可以同時生成iOS和Android兩個高性能、高保真的應用程序。

通過在不同平臺實現一個【統一接口】的【渲染引擎】來繪製UI,而【不依賴系統原生控件】,所以可以做到不同平臺UI的一致性。(自繪UI+原生)

注意:自繪引擎解決的是UI的跨平臺問題,如果涉及其它系統能力調用,依然要涉及原生開發。

簡而言之就是:保性能、保證兩個操作系統上 App 界面、功能,甚至代碼,都完全統一。

二、爲什麼用Flutter?能解決行業上的哪些痛點?
Flutter有什麼優勢?
它可以幫助你:
1.提高開發效率

  • a.同一份代碼開發iOS和Android
  • b.用更少的代碼做更多的事情
  • c.輕鬆迭代
    在應用程序運行時更改代碼並重新加載(通過熱重載)
    修復崩潰並繼續從應用程序停止的地方進行調試

2.創建美觀,高度定製的用戶體驗
受益於使用Flutter框架提供的豐富的Material Design和 Cupertino(iOS風格)的widget
實現定製、美觀、品牌驅動的設計,而不受原生控件的限制

核心原則
Flutter包括一個現代的響應式框架、一個2D渲染引擎、現成的widget開發工具。這些組件可以幫助您快速地設計、構建、測試和調試應用程序。

一切皆爲widget
Widget是Flutter應用程序用戶界面的基本構建塊。每個Widget都是用戶界面一部分的不可變聲明。 與其他將視圖、控制器、佈局和其他屬性分離的框架不同,Flutter具有一致的統一對象模型:widget

Widget可以被定義爲: 一個結構元素(如按鈕或菜單) 一個文本樣式元素(如字體或顏色方案) 佈局的一個方面(如填充) 等等…

Widget根據佈局形成一個層次結構。每個widget嵌入其中,並繼承其父項的屬性。沒有單獨的“應用程序”對象,相反,根widget扮演着這個角色。

您可以通過告訴框架使用另一個widget替換層次結構中的widget來響應事件,例如用戶交互,替換後框架會比較新的和舊的widget,並高效地更新用戶界面。
Flutter Widget組成
組合 > 集成
Widget本身通常由許多更小的、單一用途widget組成,這些widget結合起來產生強大的效果。例如,Container是一個常用的widget, 由多個widget組成,這些widget負責佈局、繪製、定位和調整大小。具體來說,Container由 LimitedBox、 ConstrainedBox、 Align、 Padding、 DecoratedBox、 和Transform組成。 您可以用各種方式組合這些以及其他簡單的widget,而不是繼承容器。
類層次結構很淺且很寬,可以最大限度地增加可能的組合數量。

您還可以通過與其他widget組合來控制widget的佈局。例如,要將widget居中,可以將其封裝在Center widget中。有填充、對齊、行、列和網格的widget。 這些佈局widget沒有自己的可視化表示。相反,他們唯一的目的是控制另一個widget佈局的某些方面。要理解widget以某種方式呈現的原因,檢查相鄰widget通常很有幫助。

二、Flutter使用什麼語言?爲什麼?
採用語言
Flutter採用了Dart作爲開發框架和widget的語言。底層圖形框架和Dart虛擬機在C /C++中實現。

考量因素
Dart運行時和編譯器支持Flutter的兩個關鍵特性的組合:
基於JIT的快速開發週期:允許使用類型的語言進行形狀更改和有狀態的熱重載;
AOT編譯器:可生成高效的ARM代碼,可以快速啓動並擁有可預測的生產部署性能。

Dart在以下主要標準上得到高分:

  • 開發人員的效率。Flutter的主要價值主張之一是通過讓開發人員使用相同的代碼庫爲iOS和Android創建應用程序,從而節省了工程資源。使用高效的語言可以進一步加速開發週期,並使Flutter更具吸引力。
  • 面向對象。絕大多數開發人員都具有面向對象開發的經驗,因此更容易學習如何使用Flutter進行開發。
  • 可預測,高性能。藉助Flutter,開發人員能夠快速創建流暢的用戶體驗。爲了實現這一點,需要能夠在每個動畫幀中運行大量的代碼。這意味着我們需要一種既能提供高性能又能提供可預測性能的語言,而不會出現會導致丟幀的週期性暫停。
  • 快速內存分配。Flutter框架使用函數式流,它很大程度上依賴於底層的內存分配器,從而有效地處理小的、短期的內存分配會非常重要,所以在缺乏此功能的語言中Flutter無法有效地工作。

三、Flutter框架使用什麼編程範式?**
Flutter是一個多範式編程環境。在Flutter中使用了過去幾十年中開發的許多編程技術。我們使用的每一個範式都是我們相信該它的優勢特別適合Flutter:

  • 組合:Flutter使用的主要範例是使用小對象,然後將它們組合在一起以獲得更復雜的對象。Flutter widget庫中的大多數widget都是以這種方式構建的。例如,Material FlatButton 類是使用MaterialButton 類構建, 該類本身使用IconTheme、InkWell、Padding、Center、Material、AnimatedDefaultTextStyle和ConstrainedBox組合 構建。該InkWell 使用內置GestureDetector。Material 是使用內置AnimatedDefaultTextStyle、NotificationListener和AnimatedPhysicalModel。等等,它們都是widget。
  • 函數式編程:整個應用程序可以僅使用StatelessWidget來構建 ,這些函數本質上是描述參數如何映射到其他函數的函數。在計算佈局或繪製圖形的底層(這些應用程序不擁有狀態,因此通常是非交互式的)例如,Icon widget本質上是一個將其參數(顏色、 icon、size)映射到佈局基本單元的函數。此外,大量使用的是不可變數據結構,包括整個Widget類層次結構以及許多支持類,如 Rect和 TextStyle也都是。Dart中的Iterable API經常用來處理框架中的值列表,它大量使用了函數式(map,reduce,where等)方法。
  • 事件驅動:用戶交互由事件對象表示,這些事件對象被分派給註冊了事件處理程序的回調。屏幕刷新也由類似的回調機制觸發。監聽類是動畫系統的基礎,它確立了與多個監聽事件訂閱模式。
  • 基於類的面向對象編程:框架的大部分API都是使用繼承類來構建的。我們使用一種方法來在基類中定義非常抽象的API,然後在子類中迭代地對它們進行定製化。例如,我們的渲染對象有一個與座標系無關的基類(RenderObject),然後我們有一個子類(RenderBox),它引入了基於笛卡爾座標系(x / width)和Y /高度)。
  • 基於原型的面向對象編程: ScrollPhysics 類將實例鏈接起來組成適用於在運行時動態滾動的physics。這使得系統可以編寫包含特定平臺physics的分頁physics,而無需在編譯時選擇平臺。
  • 命令式編程:直接命令式編程通常與對象內部封裝的狀態配對,用於提供最直觀的解決方案。例如,測試是以一種強制性風格編寫的,首先描述測試中的情況,然後列出測試必須匹配的不變量,然後根據測試需要推進時鐘或插入事件。
  • 響應式編程widget和元素樹有時被描述爲響應式的,因爲在widget的構造函數中提供的新輸入會立即作爲widget的構建方法對較低級別widget的更改傳播,並在較低widget中進行更改(例如,作爲響應到用戶輸入)通過事件處理程序傳播回widget樹。根據widget的需求,功能嚮應和命令響應兩方面都存在於框架中。具有構建方法的widget僅由一個表達式組成,該表達式描述了widget如何對其配置中的變化做出反應的功能響應widget(例如,Divider類)。 構建方法通過幾個語句構建子項列表的widget,描述了widget如何對其配置中的更改作出反應,這些都是命令性響應widget(例如 Chip類)。
  • 聲明式編程:widget的構建方法通常是具有多層嵌套構造函數的單一表達式,使用Dart的嚴格聲明子集編寫。這樣的嵌套表達式可以機械地轉換成任何適合表達的標記語言或從任何適合表達的標記語言轉換。例如, UserAccountsDrawerHeader widget具有很長的構建方法(20行以上),由單個嵌套表達式組成。這也可以與命令式風格相結合來構建用純粹聲明式方法難以描述的UI。
  • 泛型:類型可用於幫助開發人員及早發現編程錯誤。Flutter框架使用泛型編程來處理這個問題。例如, State類根據其關聯widget的類型進行參數化,以便Dart分析器可以捕獲狀態和widget的不匹配。類似地, GlobalKey類需要的類型參數,以便它可以訪問遠程widget的狀態下在一個類型安全的方式(使用運行時檢查), 路由接口是參數化時,它是預期使用類型 pop和集合,例如List、Map和 Set都是參數化的,這樣可以在分析過程中或在調試期間的運行時提前捕獲不匹配的元素。
  • 併發:Flutter大量使用 Future和其他異步API。例如,動畫系統通過Future來完成動畫完成時的通知。圖像加載系統同樣使用Future在加載完成時進行報告。
  • 約束:Flutter中的佈局系統使用弱形式的約束編程來確定場景的幾何形狀。約束(例如,對於笛卡爾盒子,最小和最大寬度以及最小和最大高度)從父母傳遞給孩子,並且孩子選擇生成的幾何結構(例如,對於笛卡爾盒子,大小,特別是寬度和高度)滿足這些限制。通過使用這種技術,Flutter通常可以通過一次遍佈整個場景。
    **四、Flutter框架組成?
    分層的框架
    Flutter框架是一個分層的結構,每個層都建立在前一層之上。
    分層框架
    這個設計的目標是幫助你用更少的代碼做更多的事情。
    例如,Material層是通過組合來自Widget層的基本Widget來構建的, 並且Widgets層本身是通過較低級對象渲染層構建的。

層爲構建應用程序提供了許多選項。選擇一種自定義的方法來釋放框架的全部表現力,或者使用構件層中的構建塊,或混合搭配。 您可以實現Flutter提供的所有現成的widget,或者使用Flutter團隊用於構建框架的相同工具和技術創建您自己的定製widget。

沒有什麼是隱藏的。您可以從高層次,統一的widget概念中獲得開發效率優勢,而不會犧牲您希望深入到下層的能力。

構建widget
您可以通過實現widget的build返回widget樹(或層次結構)來定義widget的獨特特徵 。 這棵樹更具體地表示了用戶界面的widget層次。例如,工具欄widget的build函數可能返回一個包含一些文本和各種按鈕的水平佈局。 然後,框架遞歸地構建widget,直到該所有widget構建完成,然後framework將他們一起添加到樹中。

widget的構建函數一般沒有副作用。每當它被要求構建時,widget應該返回一個新的widget樹,無論widget以前返回的是什麼。 Framework會將之前的構建與當前構建進行比較並確定需要對用戶界面進行哪些修改。

這種自動比較非常有效,可以實現高性能的交互式應用程序。而構建函數的設計則着重於聲明widget是由什麼構成的,而不是將用戶界面從一個狀態更新到另一個狀態的(這很複雜性),從而簡化了代碼。

處理用戶交互
如果widget需要根據用戶交互或其他因素進行更改,則該widget是有狀態的。例如,如果一個widget的計數器在用戶點擊一個按鈕時遞增,那麼該計數器的值就是該widget的狀態。 當該值發生變化時,需要重新構建widget以更新UI。

這些widget將繼承StatefulWidget(而不是State)並將它們的可變狀態存儲在State的子類中。在這裏插入圖片描述

每當你改變一個State對象時(例如增加計數器),你必須調用setState()來通知框架,框架會再次調用State的構建方法來更新用戶界面。 (思想同其他響應式框架,如ReactNative)

有了獨立的狀態和widget對象,其他widget可以以同樣的方式處理無狀態和有狀態的widget,而不必擔心丟失狀態。 父widget可以自由地創造子widget的新實例且不會失去子widget的狀態,而不是通過持有子widget來維持其狀態。 框架在適當的時候完成查找和重用現有狀態對象的所有工作。

五、Flutter App項目結構?
我用的是IntellijIdea,要使用Flutter,先安裝把Flutter插件,並下載FlutterSDK,插件中已經集成了DART。
在這裏插入圖片描述
新建一個App,有兩種方式:
1、在命令行中使用命令創建,再導入到IDEA在這裏插入圖片描述

Downloads Transfar$ flutter create flutter_demoex
Creating project flutter_demoex...
 

2、直接在IDEA中創建,效果是一樣的
在這裏插入圖片描述
項目基本結構如圖:
在這裏插入圖片描述
結構很清晰,提供android和IOS兩種系統應用結構,一個測試目錄
重點關注:
lib目錄- dart代碼存放路徑
pubspec.yaml-包管理文件,想引用包直接在這裏引用。

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  english_words: ^3.1.0
  css_colors: ^1.0.0
  url_launcher: ^3.0.1
  path_provider: ^0.5.0

六、FlutterSDK的源碼組成?
打開External Libraries
1、Dart管理的包
在這裏插入圖片描述
2.DART SDK在這裏插入圖片描述
3.
在這裏插入圖片描述
七、實踐:按Flutter中文網教程新建一個帶State的App.**
編寫您的第一個 Flutter App
https://flutterchina.club/get-started/codelab/
需求:
建兩個頁面,一個單詞列表頁,一個收藏列表頁。單詞列表頁每一條記錄都有一個收藏按鈕,通過該按鈕(state)改變widget狀態。單詞列表頁AppBar右邊有個頁面跳轉按鈕,跳轉到收藏列表頁。
這也是我們平時開發時最普通的功能。

7.1、創建APP的過程省略,看 五、Flutter App項目結構?
此處我們來看下android下的settings.gradle是怎麼找Flutter插件的

include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
    include ":$name"
    project(":$name").projectDir = pluginDirectory
}

local.properties裏添加FlutterSDK

flutter.sdk=/Users/Transfar/Documents/FlutterSDK/flutter

build.gradle裏設置包管理倉庫,我們用阿里雲的

buildscript {
    repositories {
        //google()
        //jcenter()
        maven{ url 'https://maven.aliyun.com/repository/google' }
        maven{ url 'https://maven.aliyun.com/repository/jcenter' }
        maven{ url 'https://maven.aliyun.com/nexus/content/groups/public' }
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.2.1'
    }
}

allprojects {
    repositories {
        //google()
        //jcenter()
        maven{ url 'https://maven.aliyun.com/repository/google' }
        maven{ url 'https://maven.aliyun.com/repository/jcenter' }
        maven{ url 'https://maven.aliyun.com/nexus/content/groups/public' }
    }
}

app的build.gradle看下是怎麼獲取到flutter的版本號和版本名的

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

7.2、使用外部包(package)
找到pubspec.yaml文件,在dependencies中添加

 english_words: ^3.1.0

在這裏插入圖片描述
工具欄中執行 flutter packages get在這裏插入圖片描述
或者直接點 packages get導入包在這裏插入圖片描述
7.3、添加一個 有狀態的部件(Stateful widget)
Stateless widgets 是不可變的, 這意味着它們的屬性不能改變 - 所有的值都是最終的.
Stateful widgets 持有的狀態可能在widget生命週期中發生變化. 實現一個 stateful widget 至少需要兩個類:

一個 StatefulWidget類。
一個 State類。 StatefulWidget類本身是不變的,但是

State類在widget生命週期中始終存在.

7.3.1 添加有狀態的 RandomWords widget類,複寫方法,實例化RandomWordsState

class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

7.3.2 添加RandomWordsState類,管理RandomWords的狀態

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];//記錄單詞對

  final _biggerFont = const TextStyle(fontSize: 18.0); //文本樣式

  final _saved = new Set<WordPair>(); //記錄被收藏的單詞,用Set是爲了確保內容唯一

7.3.3 複寫build方法,將生成單詞對的代碼從MyApp移動到RandomWordsState來生成。

@override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }

7.3.4 body:_buildSuggestions就是創建單詞列表。actions: [ ]添加跳轉按鈕,並添加交互方法_pushSaved,來分別看下這兩個方法。
a. 用ListView.builder建list,包括添加分隔線以及追加無限的記錄;

Widget _buildSuggestions() {
    return new ListView.builder(
        padding: const EdgeInsets.all(16.0),
        // 對於每個建議的單詞對都會調用一次itemBuilder,然後將單詞對添加到ListTile行中
        // 在偶數行,該函數會爲單詞對添加一個ListTile row.
        // 在奇數行,該函數會添加一個分割線widget,來分隔相鄰的詞對。
        // 注意,在小屏幕上,分割線看起來可能比較吃力。
        itemBuilder: (context, i) {
          // 在每一列之前,添加一個1像素高的分隔線widget
          if (i.isOdd) return new Divider();

          // 語法 "i ~/ 2" 表示i除以2,但返回值是整形(向下取整),比如i爲:1, 2, 3, 4, 5
          // 時,結果爲0, 1, 1, 2, 2 這可以計算出ListView中減去分隔線後的實際單詞對數量
          final index = i ~/ 2;
          // 如果是建議列表中最後一個單詞對
          if (index >= _suggestions.length) {
            // ...接着再生成10個單詞對,然後添加到建議列表
            _suggestions.addAll(generateWordPairs().take(10));
          }
          return _buildRow(_suggestions[index]);
        });
  }

b.再來看下_buildRow方法,onTap管理收藏列表狀態;

Widget _buildRow(WordPair suggestion) {
    final alreadySaved = _saved.contains(suggestion);
    return new ListTile(
      title: new Text(
        suggestion.asPascalCase,
        style: _biggerFont,
      ),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
          if (alreadySaved) {
            _saved.remove(suggestion);
          } else {
            _saved.add(suggestion);
          }
        });
      },
    );
  }

c.跳轉交互方法_pushSaved,直接跳轉到收藏頁面

(在Flutter中稱爲路由(route),在主路由和新路由之間導航(切換頁面))

void _pushSaved() {
    Navigator.of(context).push(//添加Navigator.push調用,這會使路由入棧(以後路由入棧均指推入到導航管理器的棧)
      new MaterialPageRoute(//添加MaterialPageRoute及其builder
        builder: (context) {
          final tiles = _saved.map(
            (pair) {
              return new ListTile(
                title: new Text(
                  pair.asPascalCase,
                  style: _biggerFont,
                ),
              );
            },
          );
          final divided = ListTile.divideTiles(//ListTile的divideTiles()方法在每個ListTile之間添加1像素的分割線。  divided 變量持有最終的列表項。
            context: context,
            tiles: tiles,
          ).toList();
          return new Scaffold(
            appBar: new AppBar(
              title: new Text('Saved Suggestions'),
            ),
            body: new ListView(children: divided),
          );
        },
      ),
    );
  }

7.4、最後返回main.dart,替換MyApp build爲下面的代碼

void main() => runApp(MyApp())//入口函數
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return new MaterialApp(
      title: 'Startup Name Generator',
      theme: new ThemeData(//自定義一個主題
        primaryColor: Colors.redAccent,
      ),
      home: new RandomWords(), //實例化RandomWords,畫UI
    );
  }

7.5、效果
在這裏插入圖片描述
在這裏插入圖片描述
不用自己去實現返回的交互

在Flutter中,導航器管理應用程序的路由棧。將路由推入(push)到導航器的棧中,將會顯示更新爲該路由頁面。
從導航器的棧中彈出(pop)路由,將顯示返回到前一個路由。

通過這個例子,我們學到了以下內容:

  • 從頭開始創建一個Flutter應用程序.
  • 編寫 Dart 代碼.
  • 利用外部的第三方庫.
  • 使用熱重載加快開發週期.
  • 實現一個有狀態的widget,爲你的應用增加交互.
  • 用ListView和ListTiles創建一個延遲加載的無限滾動列表.
  • 創建了一個路由並添加了在主路由和新路由之間跳轉邏輯
  • 瞭解如何使用主題更改應用UI的外觀.

八、總結
我們初始了Flutter,大概瞭解了Flutter架構和SDK,以及怎麼去實現一個Flutter應用。要深入學習的內容很多,比如構建、打包、發佈,與原生組件通信等等,路很長,大家一起學。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章