Flutter學習之入門和體驗

真丶深紅騎士發表於2019-03-12

一、前言

1.什麼是Flutter

上週我的一位微信好友問我有沒有學Flutter,我回答說還沒真正學,他說應該要接觸一下。對於新技術的誕生,我始終保持敬畏之心,和另一位大學舍友聊了當時如何入坑Android的經歷,才發現自己的學習方式和路線有很多的問題,知識點很零亂,知識沒有系統化,不多說了,後面學習新的知識一定要從“碎片化”到“整體化”。2018年2月,在世界移動大會上,Google釋出了Flutter的第一個beta版本,2018年6月11日釋出首個預覽版,在2018年12月05日北京時間凌晨1點45分,在Flutter Live上,谷歌Flutter團隊推出Flutter1.0,Flutter1.0版本是UI工具包的第一個穩定版本,在2019年2月27日世界行動通訊大會上Google推出1.2版本,帶來全新的Web開發工具。另外今日頭條團隊即將開源讓Flutter真正支援View級別的混合開發(上層Flutter Framework引入Widget/LayerTree等概念自己實現了介面描述框架,下層Flutter Engine把LayerTree用OpenGL渲染成使用者介面),閒魚團隊也開源了基於 Redux資料管理的組裝式Flutter應用框架。什麼是Flutter呢?Flutter是一個跨平臺的免費開源的移動UI框架,是Google的移動應用SDK,用於在極短時間內在iOS和Android平臺上建立高質量的原生體驗,簡而言之就是在iOS下和Android下共用一套程式碼,一套程式碼就能在兩個作業系統下執行,其官方程式語言是是Dart,學習這門語言很快就上手。Flutter提供很多豐富的UI元件庫,開發者可以快速開發出靈活的UI介面,另外Flutter已經加入Material Design元件大家庭中,也就是說Flutter可以使用Material Theming和Material 元件,可以相信未來會有更多的創意設計UI風格會湧現。

2.Flutter的特性

  1. 快速開發,Flutter的熱過載可幫助快速輕鬆試驗,構建UI,新增功能和快速修復錯誤,在iOS和Android的模擬器和硬體上體驗亞秒級過載同時不會丟失狀態,這裡直接引用官方的圖:
    熱過載圖
  2. 機具表現力和美觀UI,Flutter內建的Material Design和Cupertino(iOS風格)的部件、豐富的手勢API、自然平滑的滑動和不同的平臺表現來提升使用者體驗,直接看下圖:
    Flutter美觀UI圖
  3. 現代化響應式框架,使用現代化響應式框架和豐富的平臺、佈局和基礎元件來構建使用者介面,使用功能強大且靈活的API(針對2D,動畫,手勢,動效等)解決複雜的使用者介面設計。
class CounterState extends State<Counter> {
  int counter = 0;

  void increment() {
    // Tells the Flutter framework that state has changed,
    // so the framework can run build() and update the display.
    setState(() {
      counter++;
    });
  }

  Widget build(BuildContext context) {
    // This method is rerun every time setState is called.
    // The Flutter framework has been optimized to make rerunning
    // build methods fast, so that you can just rebuild anything that
    // needs updating rather than having to individually change
    // instances of widgets.
    return new Row(
      children: <Widget>[
        new RaisedButton(
          onPressed: increment,
          child: new Text('Increment'),
        ),
        new Text('Count: $counter'),
      ],
    );
  }
}
複製程式碼
  1. 使用平臺原生功能及SDK,通過平臺API,第三方SDK和原生程式碼讓英語具有強大的擴充套件性,Flutter允許重複使用現有的Java,Swift和Object程式碼,並訪問iOS和Android上的原生功能和SDK,例如下圖獲取手機電量數值:
Future<Null> getBatteryLevel() async {
  var batteryLevel = 'unknown';
  try {
    int result = await methodChannel.invokeMethod('getBatteryLevel');
    batteryLevel = 'Battery level: $result%';
  } on PlatformException {
    batteryLevel = 'Failed to get battery level.';
  }
  setState(() {
    _batteryLevel = batteryLevel;
  });
}
複製程式碼
  1. 統一應用開發體驗:Flutter豐富的工具庫可以讓開發者輕鬆在iOS和Android裝置上實現自己的想法,可以充分利用現有的大部分Java、Object-C或者Swift程式碼。

3.2019年Flutter產品線

2019年一月底,Flutter團隊公佈了2019年Flutter的產品線,為以下幾點確定了明確的計劃:

  1. 核心基礎:Bug修復,效能調優,記憶體診斷工具優化,改進測試流程。
  2. 生態系統:更好的C/C++庫支援,推進官方開發/維護的Packages達到與核心框架相同質量和完整性。
  3. 動態更新:在國內,這是必不可少的,提供Android上的動態修復:程式碼更新從伺服器推送到Android應用裡,也就是熱更新。
  4. 支援開發移動端web應用。 並且Flutte UX研究團隊會定期根據使用者反饋來助力打造更優的Flutter,根據使用者反饋來調整開發重點,可見Flutter UX團隊的用心和付出程度,今年拭目以待。

二、Flutter架構

1.架構圖

Flutter框架從到下包含三部分:函式式響應的Framework(Dart),Engine(C++),Embedder(Platform Specific)。下面直接上圖:

官方架構圖

  • FrameWork是用Dart實現,提供了Material風格的小部件、Cupertino風格的小部件(用於iOS)、文字影象按鈕元件、動畫、手勢等。
  • Engine時用C++實現的,上圖詳細了,實際包含三部分:Skia、Dart、Text。Skia是一個開源的2D圖形庫,為硬體和軟體平臺提供API。Dart包括Dart執行時和垃圾回收。Flutter在除錯模式下執行,由JIT(Just in Time,執行時編譯,邊執行邊編譯)支援,如果在釋出模式下,則是通過AOT(Ahead of Time,執行前編譯,普通的靜態編譯)將Dart程式碼編譯原生的"arm"程式碼。Text是指文字渲染庫。
  • Embedder是指嵌入器,就是可將Flutter嵌入到各種平臺中,它的主要任務是渲染Surface設定、執行緒設定和外掛。

2.渲染過程

渲染過程一
渲染過程二
這裡結合上面兩張圖來看,可以知道,當需要更新UI的時候,Framework通知Engine,Engine會等到下個Vsync訊號到達的時候,會通知Framework,然後Framework會進行Animate,Build,Layout,Paint,最後生成Layer。Compositor 把所有的Layer組合成Scene,再通過Skia進行處理,最後Engine通過GPU GI介面提交資料給GPU,GPU最後經過處理後在顯示器顯示。上面簡單瞭解什麼Flutter,Flutter特性和框架,有了初步的瞭解,下面就開始一步一步實現第一個Flutter應用。

三、Flutter環境搭建

1.獲取Flutter SDK

務必電腦安裝git,本文是在Windows環境下配置的,MAC下配置環境流程是一致的。首先要獲取Flutter SDK,使用git去克隆倉庫然後新增Flutter工具到自己的環境變數,執行flutter doctor來顯示剩下需要安裝的依賴,國內使用者最好配置一下兩個環境變數:

PUB_HOSTED_URL=https://pub.flutter-io.cn
複製程式碼

如下圖所示:

配置環境變數一

FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
複製程式碼

如圖所示:

配置環境變數二
因為這次我第一次在電腦上安裝Flutter,所以就要克隆這個遠端倉庫,我在電腦F盤下執行下面git命令:

$ git clone -b beta https://github.com/flutter/flutter.git
複製程式碼

如圖所示:

git命令下載Flutter SDK
下載比較慢,等了十多分鐘才下載完。下載完看到F盤果然有一個名字叫flutter的資料夾,接著將克隆下來的專案bin目錄配置到環境變數Path去,因為我是下載到F盤,所以下圖路徑是:
flutter_sdk路徑
如果需要更新flutter的sdk,在命令列執行flutter upgrade命令即可。

2.執行flutter doctor

開啟一個新的命令提示符視窗,執行下面命令flutter doctor,看是否需要安裝任何依賴項來完成安裝,這個命令會檢測環境和在終端生成報告,Dart SDK和Flutter捆綁在一起,沒必要單獨去安裝Dart。初次執行它會下載自己的依賴庫並且自行編譯,可能比較慢。後續執行flutter命令就會很快。這裡注意,如果CMD視窗顯示亂碼問題,下面是解決方案:

  • Win+R進入進入CMD命令列輸入regedit進入登錄檔
  • 找到 HKEY_CURRENT_USER\Console\%SystemRoot%_system32_cmd.exe 如果該項下已存在CodePage項,則把值改為十進位制”65001”,如果不存在,在該項下新建一個 DWORD(32位值),命名為“CodePage”,值設為“65001”
  • 重啟cmd後生效
    flutter中文亂碼
    輸入flutter doctor執行結果截圖所示:
    flutter doctor第一次
    注意看到Android toolchain選項 意思是得要執行flutter doctor --android-licenses同意協議才可以安裝Android工具鏈。輸入flutter doctor --android-licenses一直按y即可。最後重新輸入flutter doctor,結果如下圖:
    flutter doctor最終結果
    從上面執行結果可以知道一下資訊:
  • Flutter的版本和渠道已經安裝
  • Flutter執行需要的Android工具鏈已安裝
  • Android studio開發工具已經安裝
  • flutter外掛和Dart外掛沒有安裝(在Android stdio安裝下面會說)
  • 沒有連線手機

3.搭建Android Studio開發環境

在Android Studio--File--Settings --plugins搜尋Flutter即可,如下圖:

安裝Flutter外掛
點選安裝的時候會彈出一下提示框:
Dart外掛安裝
大概意思是安裝Flutter外掛需要Dart外掛的支援,就是需要和Dart外掛一起安裝,安裝完成後,重啟開發工具,就可以新建和開發程式了。 注意:如果提示cannot download xxxx的話去plugins.jetbrains.com/搜尋外掛名字,下載對應的外掛後解壓到對應的位置,如下圖:
外掛位置
或者點選外掛本地安裝:
本地安裝外掛
最後重啟自己的編譯器即可。

四、建立第一個Flutter程式

1.New Flutter Application

因為現在是要建立應用程式,因此在Create New Flutter Project下選擇Flutter Application

選擇應用程式

2.配置專案資訊

在下一個頁面輸入專案名字,配置Flutter SDK目錄,專案存放的路徑,專案的描述,公司的域名,專案的包名如圖所示:

專案配置
最後一步
最後點選finish等待Android Studio完成所要建立的Flutter專案了。

3.專案目錄

專案建立完成後,編輯器給我們生成的目錄如下:

專案目錄

  • android:Andorid相關程式碼目錄,裡面程式碼配置和單獨建立Andorid專案有些不一樣
  • ios:iOS相關程式碼目錄,存放Flutter與ios原生互動的一些程式碼
  • lib:應用原始檔,dart檔案,核心檔案,可以建立不同的資料夾,存放不同檔案
  • test:測試檔案
  • .gitignore:忽略檔案,記錄一些不需要關注變更記錄的檔案,就是不新增到版本記錄裡面
  • .metadata:記錄一些Flutter project一些基本資訊,如版本,專案型別
  • .packages:記錄一些lib檔案的路徑
  • .iml:是由IntelliJ IDEA建立的模組檔案,用於開發Java應用程式的IDE。它儲存有關開發模組的資訊,該模組可能是Java,Plugin,Android或Maven元件; 儲存模組路徑,依賴關係和其他設定
  • pubspec.lock:這是根據當前專案依賴所生成的檔案,記錄當前使用的依賴版本
  • pubspec.yaml:包含Flutter應用程式的包資料,這個是配置依賴項的檔案,比如配置遠端public倉庫的依賴項,或者本地資源(圖片,音視訊)
  • README.md:根據意思就是“讀我”,這裡會記錄一些專案結構,詳細資訊,幫助資訊,瞭解一個專案都是通常通過這個檔案入手。

總體來看和原生Android的工程結構不一樣了,因為程式碼都是在lib目錄完成的,所以不能用Android多module多lib結構去建立module和lib,除非要用到原生互動的程式碼,可以在android目錄裡面去寫,然後在lib目錄裡面去引用。相對Android開發者而言,多了ios目錄,ios開發者而言,多了android目錄,其他檔案上面有具體詳細說明。因為lib目錄是開發者最主要關注的,開啟lib目錄,發現有個字尾是dart的檔案,裡面內容都是用dart語法來寫的,還發現有void main() => runApp(MyApp());main()函式對於學過java而言都非常清晰,這個應該是入口函式,另外發現StatelessWidget,StatefulWidget一些小控制元件,這裡想應該是UI元件吧。這裡先不管那麼多,直接點選執行圖示,執行效果圖如下:

專案第一次執行效果圖
走到這裡,說明專案環境配置成功,併成功執行第一個簡單的程式。

五、編寫第一個應用

1.編寫“Hello world”

現在還是按照新手走,先自己擼個“Hello world”出來吧。把main.dart中所有程式碼去掉,替換下面程式碼,螢幕中心顯示Hello World

import 'package:flutter/material.dart';

//這個是Dart中單行函式或者方法的簡寫
void main() => runApp(MyApp());

//程式繼承StatelessWidget,該應用程式成為一個widget,在Flutter中,大多數東西都是widget
class MyApp extends StatelessWidget {
  // 這個是應用的根widget
  @override
  Widget build(BuildContext context) {
    //注意:一個app只能有一個MaterialApp
    return MaterialApp(
      //標題欄的名字
      title: 'Hello Flutter',
      //這個是Material library提供的一個widget,它提供了預設的導航欄、標題欄
      //包含主螢幕的widget樹的body屬性
      home:new Scaffold(
        appBar:new AppBar(
          title:const Text("Weclome to Flutter"),
        ),
        body:const Center(
          child:const Text("Hello World"),
        ),
    ),
    );
  }
}
複製程式碼

Android模擬器執行效果如下:

Android模擬器效果
iOS模擬器執行效果如下:
iOS效果執行
感覺在iOS執行效果比Android上好多了。看到這裡發現Android並不是沉浸式狀態列,而iOS預設就是沉浸式了,那有沒有辦法也能讓Android版本也做成沉浸式呢,答案是肯定有的,一開始我的思路是應該是在main方法裡判斷是不是Android版本,如果是就設定,網上找裡找,這種方法果然可行:

  1. 首先先導包:
import 'dart:io';
import 'package:flutter/services.dart';
複製程式碼
  1. 在mian方法根據Android版本做設定:
//入口函式
void main() {
  //MaterialApp元件渲染後
  runApp(MyApp());
  //判斷如果是Android版本的話 設定Android狀態列透明沉浸式
  if(Platform.isAndroid){
    //寫在元件渲染之後,是為了在渲染後進行設定賦值,覆蓋狀態列,寫在渲染之前對MaterialApp元件會覆蓋這個值。
    SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
    SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
  }
}
複製程式碼

還有另外一種方法,因為Flutter主入口只有一個MainActivity,也就是說所有的Flutter頁面都會執行這個MainActivity,那我們只需要在這個主入口判斷一下版本號然後將狀態列顏色設定成透明,具體位置在android->app->src->main->xxx->MainActivity:

public class MainActivity extends FlutterActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //設定狀態列透明
    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)
    {//API>21,設定狀態列顏色透明
      getWindow().setStatusBarColor(0);
    }
    GeneratedPluginRegistrant.registerWith(this);
  }
}
複製程式碼

最終效果Android和iOS下執行如下:

沉浸式統一
這樣效果好很多了。另外發現,Android下的標題欄是左邊對齊的,那怎麼做成iOS那樣標題欄在中間呢?很簡單,在AppBar加上Center widget就可以了,程式碼如下:

appBar:new AppBar(
          //ios和android標題欄統一在中間
          title:new Center(child :const Text("Weclome to Flutter")),

        ),
複製程式碼

最終效果如下:

ios和Android統一
這樣真正做到了iOS和Android版本統一了。上面程式碼app繼承了StatelessWidget,這樣本身也成為了widget,在Flutter中,很多時候一切都看作是widget,layout和padding等等。上面例子結構很清晰明瞭,就是StatelessWidget類包含了(應用欄)Appbar,和構成主頁面widget樹結構的body屬性。而body的widget又包含了一個Center widget,Center widget又包含一個Text子widget,Center widget可以將子widget對齊到螢幕中心。

2.使用外部package

現在,通過在pubspec.yaml檔案配置依賴項,去依賴一個提供英文單詞的包,在pub.dartlang.org/flutter/這個網站可以找到很多開源軟體包,可以搜尋指定的軟體包檢視對應版本。

開源軟體包

  1. pubspec檔案管理著Flutter應用程式的靜態資原始檔,那在pubspec.yaml檔案,將english_words(3.1.5版本)新增到依賴項,這裡要注意:和^之間是有空格的,不能會出錯。如下所示:
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.5
複製程式碼
  1. 這時候點選編輯器的右上角的Packages get,就是把package拉取到專案中去,並且可以看到控制檯輸出:Runningflutter packages getin flutter_demo...
    packages獲取
    控制檯輸出
  2. 在lib/main.dart下,將english_words匯入,顯示灰色證明你匯入的庫沒有使用,如下圖所示:
    english_words匯入
  3. 改用英文單詞的package來生成文字,程式碼改為如下:
//程式繼承StatelessWidget,該應用程式成為一個widget,在Flutter中,大多數東西都是widget
class MyApp extends StatelessWidget {
  // 這個是應用的根widget
  @override
  Widget build(BuildContext context) {
    //隨機生成函式
    var wordPair = new WordPair.random();
    return MaterialApp(
      //標題欄的名字
      title: 'Hello Flutter',
      //這個是Material library提供的一個widget,它提供了預設的導航欄、標題欄
      //包含主螢幕的widget樹的body屬性
      home:new Scaffold(
        appBar:new AppBar(
          //ios和android標題欄統一在中間
          title: new Center(child: const Text("Weclome to Flutter")),
        ),
        body:new Center(
          //使用隨機生成的英文單詞 為什麼不能const呢 因為 內容發生變化 const是用來修飾常量的
          child:new Text(wordPair.asPascalCase),
        ),
    ),
    );
  }
}
複製程式碼

注意的是Center widget 和 Text widget要用new 來建立,因為內容修改了,並不是常量了,這時候按儲存就可以看到新的單詞出現在螢幕中間文字了。

3.新增有狀態的widget

上面MyApp是繼承StatelessWidget,StatelessWidget是無狀態的也是不可控的,意思是其屬性是不能改變的,所有的值都是最終的。在平時開發中,很多控制元件都需要根據特定場景改變自身的狀態,那麼Flutter有沒有提供有狀態的widget呢?答案肯定是有的,Statefulwidget是有狀態的,在其生命週期保持的狀態可能會變化,實現一個有狀態的widget至少需要兩個類:StatefulWidgets類和State類。

  1. 新增RandomWordsWidget類,這個類繼承StatefulWidget類,這個類新增到main.dart檔案最底下:
//建立有狀態的widget
class RandomWordsWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() {
    return new RandomWordsState();
  }
}
複製程式碼
  1. 新增RandomWordsState了,這個類會儲存RandomWordsWidget的狀態,後面會儲存一些特殊不同狀態下的片語,並且在裡面新增build方法,不然會報錯,把生成單詞的程式碼放到這個build類中去,如下圖:
//用來儲存RandomWords widget的狀態
class RandomWordsState extends State<RandomWordsWidget>{
  @override
  Widget build(BuildContext buildContext){
    var wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);

  }
}
複製程式碼
  1. MyApp下改成建立RandomWordsWidget即可:
//程式繼承StatelessWidget,該應用程式成為一個widget,在Flutter中,大多數東西都是widget
class MyApp extends StatelessWidget {
  // 這個是應用的根widget
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      //標題欄的名字
      title: 'Hello Flutter',
      //這個是Material library提供的一個widget,它提供了預設的導航欄、標題欄
      //包含主螢幕的widget樹的body屬性
      home:new Scaffold(
        appBar:new AppBar(
          //ios和android標題欄統一在中間
          title: new Center(child: const Text("Weclome to Flutter")),
        ),
        body:new Center(
          child:new RandomWordsWidget()
        ),
    ),
    );
  }

複製程式碼

這時候執行的效果還是跟之前一樣,不過實現方式不一樣。

4.建立ListView

下面建立一個滑動元件ListView,開發者接觸最多應該是這個滑動元件,下面實現當使用者滑動列表的時候,ListView會不斷增長,不斷顯示新的單詞。

  1. 在RandomWordsState這個類建立一個陣列列表,用來儲存片語,這個變數以下劃線為開頭,Dart語言中下劃線字首是表示強制私有
final _normalWords = <WordPair>[];
複製程式碼
  1. 在RandomWordsState類新增_buildNormalWords方法,用於構建一個ListView,ListView提供了itemBuilder屬性,這是一個工廠builder作為匿名函式進行回撥,這個函式需要傳入兩個引數,一個是BuildContext上下文和行迭代器。對於ListView每一行都會執行這個函式呼叫,這裡想想好像是平時Android開發中ListView建立新增item的方法。
//建立填充單詞的ListView
  Widget _buildNormalWorlds() {
    //內容上下16dp
    return new ListView.builder(
        padding: const EdgeInsets.fromLTRB(0, 16, 0, 16),
        //每個單詞都會條約一次itemBuilder,然後將單詞新增到ListTile中
        itemBuilder: (context, i) {
          //首先建立10條單詞
          if (i >= _normalWords.length) {
            //接著再生成10個單詞,新增到列表上
            _normalWords.addAll(generateWordPairs().take(10));
          }
          return _buildItem(_normalWords[i]);
        });
  }
複製程式碼
  1. 因為ListView是需要設定Item項的樣式,這裡同樣在RandomWordsState裡新增_buildItem方法,用來載入指定資料,這裡指定內容居中顯示:
  //設定每個item項的內容和樣式
  Widget _buildItem(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        textAlign: TextAlign.center,
      ),
    );
  }
複製程式碼
  1. 更改RandomWordsState的build方法,增加_buildNormalWords方法的呼叫,不是直接用單生成庫,代方法如下:
  Widget build(BuildContext buildContext) {
//    var wordPair = new WordPair.random();刪掉
//    return new Text(wordPair.asPascalCase);刪掉
      return new Scaffold(
        appBar: new AppBar(
          title: new Center(child: const Text("Weclome to Flutter")),
        ),
        body:_buildNormalWorlds(),
      );
  }
複製程式碼
  1. 更改MyApp的build方法,因為標題的設定放在了RandomWordsState裡了,所以不需要再額外新增,並將home變成RandomWords widget,程式碼一下子簡潔很多如下:
//程式繼承StatelessWidget,該應用程式成為一個widget,在Flutter中,大多數東西都是widget
class MyApp extends StatelessWidget {
  // 這個是應用的根widget
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home:new RandomWordsWidget(),
    );
  }
}
複製程式碼

最終效果如圖所示

flutter第一個列表

  1. 下面新增分割線,讓UI更美觀,這裡通過因為計算機裡迴圈開始都是從0開始的,我_buildNormalWorlds方法裡判斷是奇數項的話就構造出分割線新增到ListView,這裡注意分割線也是一項,那麼生成單詞的時候要稍微處理下,具體程式碼如下:
//建立填充單詞的ListView
  Widget _buildNormalWorlds() {
    //內容上下16dp
    return new ListView.builder(
        padding: const EdgeInsets.fromLTRB(0, 16, 0, 16),
        //每個單詞都會條約一次itemBuilder,然後將單詞新增到ListTile中
        itemBuilder: (context, i) {
          //新增分割線
          //奇數行,會新增一個分割線的widget,分割上下單詞
          //偶數行,就正常構造新增ListTitle row
          //構造一畫素的分割線 也可以自己去定義寬高
          //i.isOdd判斷是否奇數,奇數行新增分割線
          if(i.isOdd){
            //注意:這裡執行後會跳出迴圈
            return new Divider();
          }

          //這裡對2求商,就是計算出ListView中減去分割線後的實際單詞數量
          //如 i為 1,2,3,4,5,那麼商結果是0,1,1,2,2
          //1 除以 2商是0 2除以2商是1 以此類推
          final int index = i ~/2;

          //首先建立10條單詞
          if (index >= _normalWords.length) {
            //接著再生成10個單詞,新增到列表上
            _normalWords.addAll(generateWordPairs().take(10));
          }
          return _buildItem(_normalWords[index]);
        });
  }
複製程式碼

實際效果如下:

新增分割線後的ListView

5.新增互動

  1. 在RandomWordsState裡新增一個_collected的Set集合,這個集合用來存放收藏後的單詞,用Set的原因是Set本身的特性不允許元素有重複值:
final Set<WordPair> _collected = new Set<WordPair>();
複製程式碼
  1. _buildItem方法中新增isCollected來檢查單詞是否新增到收藏裡
final bool isCollected = _collected.contains(pair);
複製程式碼
  1. _buildItem以後置屬性來新增一個❤️圖示到ListTiles,matrial包下有預設的❤️圖示,這裡就設定下顏色就可以,程式碼如下:
return new ListTile(
      title: new Text(
        pair.asPascalCase,
        textAlign: TextAlign.center,
      ),
      //trailing 是後置圖示屬性
      trailing: new Icon(
        //material 包下 icons.dart
        isCollected ? Icons.favorite : Icons.favorite_border,
        //圖示顏色設定
        color:isCollected ? Colors.red : null,
      ),
    );
複製程式碼

執行結果後發現❤️型圖示新增到每一行最右邊處。

  1. 下面增加點選事件,當點選列表時,如果單詞沒被收藏,那麼就新增收藏,並將心形圖示改為收藏後的圖示,如果單詞被收藏了,那麼就移除處收藏,並將心形圖示變為預設的心形圖示,程式碼如下:
      //trailing 是後置圖示屬性
      trailing: new Icon(
        //material 包下 icons.dart
        isCollected ? Icons.favorite : Icons.favorite_border,
        //圖示顏色設定
        color:isCollected ? Colors.red : null,
      ),

      //item的點選事件屬性
      onTap:(){
        //狀態設定
        setState(() {
          //如果收藏了
          if(isCollected){
            //那就將set集合裡移除
            _collected.remove(pair);
          } else {
            //新增
            _collected.add(pair);
          }
        });

      }
複製程式碼

效果如下:

增加點贊後效果

  1. 下面實現跳轉新頁面,首先新增一個收藏單詞頁面,在Flutter中,導航器管理應用程式的路由棧,什麼是路由棧呢?路由棧就是用來管理路由(頁面)。就是我現在要開啟一個新的頁面,那麼這個新的頁面就會壓入路由棧中,如果我現在在新的頁面,點選返回到上一個頁面,那麼路由棧會彈出這個新頁面路由,將顯示返回到前一個頁面,這裡想想和Android原生用棧管理頁面的方法一樣,首先在首頁面增加跳轉新頁面圖示,程式碼如下:
Widget build(BuildContext buildContext) {
      return new Scaffold(
        appBar: new AppBar(
          title: new Center(child: const Text("Weclome to Flutter")),
          //增加圖示和點選事件動作 Icons.list是icon的型別 onPressed新增點選事件 點選會執行_collectWordsPage方法
          actions:<Widget>[
            new IconButton(icon: const Icon(Icons.list),onPressed: _collectWordsPage),
          ],

        ),
        body:_buildNormalWorlds(),
      );
  }
複製程式碼

實際執行後,AppBar導航欄最右邊的位置新增了一個圖示,這時候點選沒有任何反應,因為_collectWordsPage沒有任何程式碼,下面新增跳轉到新頁面的實現。

  1. 在_collectWordsPage新增Navigator.push,這個簡單易懂,就是會把頁面入棧
  //跳轉新頁面開始
  void _collectWordsPage(){
       Navigator.of(context).push(
         
         
       );
  }
複製程式碼
  1. 下面新增MaterialPageRoute和builder,新增構建ListTile行的程式碼,並新增項與項之間的分割線,最後通過toList來轉換。MaterialPageRoute是一種模態路由,可以通過平臺自適應來切換螢幕,Android而言,頁面推送過渡向上滑動頁面,淡入淡出,彈出過渡則是向下滑動頁面。IOS而言,頁面從右側滑入,反向彈出,當另一個頁面進入覆蓋時,該頁面向左移動。
  //跳轉新頁面開始
  void _collectWordsPage() {
    Navigator.of(context).push(
      new MaterialPageRoute<void>(
        builder: (BuildContext context) {
          //傳遞收藏後的單詞
          final Iterable<ListTile> tiles = _collected.map((WordPair pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase, //單詞
              ),
            );
          },
          );
        },

      ),
    );
  }
複製程式碼
  1. 建立Scaffold,其中包含標題欄,body由ListView組成,每一個Item項之間有一條分割線,執行發現,新的路由會自動新增返回按鈕,這是因為Navigator會在應用欄自動新增一個“返回”按鈕,不需要呼叫Navigator.pop,點選返回按鈕會返回到主介面,程式碼如下:
//跳轉新頁面開始
  void _collectWordsPage() {
    Navigator.of(context).push(
      new MaterialPageRoute<void>(
        builder: (BuildContext context) {
          //傳遞收藏後的單詞
          final Iterable<ListTile> tiles = _collected.map((WordPair pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase, //單詞
              ),
            );
          },
          );
          //新增分割線開始
          final List<Widget> divided = ListTile.divideTiles(
            tiles: tiles,
            context:context,
          ).toList();
          return new Scaffold(
            appBar: new AppBar(
              title: new Center(child: const Text("Collect Words")),
            ),
            body: new ListView(children: divided),
          );
        },

      ),
    );
  }
複製程式碼

最終執行效果發現標題並不是居中對齊,因為預設的有邊距,這時候需要自定義AppBar就可以,程式碼如下:

          return new Scaffold(
            appBar: new AppBar(
              titleSpacing: 0.0,
              //MaterialPageRoute這個自帶了返回鍵 下面的屬性設定取消返回鍵
              automaticallyImplyLeading:false,
              title: new Container(decoration: new BoxDecoration(color: new Color(0x00000000),
              ),
              child:new Stack(
                children: <Widget>[
                  new Container(
                    //左邊位置
                    alignment: Alignment.centerLeft,
                    child:new IconButton(
                      //圖示樣式
                      icon:new Icon(Icons.arrow_back),
                      //點選事件
                      onPressed: (){
                        Navigator.pop(context);
                      }
                    ),
                  ),
                  new Center(child:new Text("Collect Words")),
                ],
              ),)
            ),
            body: new ListView(children: divided),
          );
複製程式碼

最終執行效果如下,左邊是IOS,右邊是Android:

最終演示版

六、總結

入門Flutter的第一天,簡單知道了一下幾點:

  1. Flutter的特性和框架構成,它和大多數構建移動應用的工具不一樣,它是用自己的渲染引擎來繪製Widget,它中間層只有C/C++程式碼,Flutter使用Dart語言實現系統的絕大部分功能(佈局,動畫,手勢)。
  2. 開發環境的搭建。
  3. 體驗Flutter新增UI的步驟。
  4. 路由(頁面)的跳轉。

若有錯誤,歡迎指正~

相關文章