Flutter學習之入門和體驗

一、前言

1.什麼是Flutter

上週我的一位微信好友問我有沒有學Flutter,我回答說還沒真正學,他說應該要接觸一下。對於新技術的誕生,我始終保持敬畏之心,和另一位大學舍友聊了當時如何入坑Android的經歷,才發現自己的學習方式和路線有很多的問題,知識點很零亂,知識沒有系統化,不多說了,後面學習新的知識一定要從“碎片化”到“整體化”。2018年2月,在世界移動大會上,Google發佈了Flutter的第一個beta版本,2108年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的話去https://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文件配置依賴項,去依賴一個提供英文單詞的包,在https://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. 路由(頁面)的跳轉。

若有錯誤,歡迎指正~

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