Flutter 入門例項

黃馬發表於2018-06-26

Dart4Flutter - 01 – 變數、型別和函式

Dart4Flutter – 02 –控制流 和異常

Dart4Flutter – 03 – 類和泛型

Dart4Flutter – 04 – 非同步和庫

Dart4Flutter - 拾遺01 - flutter-dart環境搭建

Dart4Flutter - 不可變性

Flutter入門 - 狀態管理

Flutter 入門例項1

Flutter 入門 - Container 屬性詳解

Flutter 入門-本地訪問-MethodChannel

Flutter 例項 - 載入更多的ListView

Flutter 例項 - 從本地到Flutter通訊 - Event Channels

Flutter 入門例項

自從10年前Android和iOS出現,在移動開發界,跨平臺開發一直是一個目標。一個跨平臺的應用可以幫助公司和團隊節省大量的時間和精力。

過去這些年,已經發布了很多跨平臺的開發工具,包括來自adobe基於web的PhoneGap,來自Microsoft的Xamarin,來自Facebook的React Native。

最近進入跨平臺界的工具是來自google的flutter,現在已經發布了第一個預覽版。flutter擁有開發效率高、UI渲染快,更容易的自定義元件、和在兩個平臺上和本地應用一樣的效能。

介紹

flutter應用是用dart語言開發的,Dart和其他語言,例如Kotlin Swift一樣,擁有很多共同的語言特性,而且可以轉化為JavaScript程式碼。

作為一個跨平臺框架,flutter非常像React Native,例如flutter支援響應式和宣告是程式設計。但是和React Native不一樣的是,flutter不需要JavaScript橋接,這樣可提升整個應用的效能和應用啟動的時間。Dart是通過AOT技術取得了上述的表現。

另一方面,擁有JIT的Flutter,支援在開發期間,不需要從頭構建整個應用,就可以重新整理UI,提供開發效率。

正如本教程將要展示的,flutter框架是建立在元件思想之上的。在flutter中,元件不單單指應用的檢視,而是整個介面甚至是真個應用。

除了開發跨Android和iOS應用,學習flutter將給你一個在Fuchsia平臺開發的超前開始。Fuchsia是google開發中的實驗性的作業系統。

本教程,我們構建一個app,首先從github api中獲取一個組織的成員資訊,然後將成員資訊展示在一個滾動列表中。

Flutter 入門例項

當開發app時,可是使用Android模擬器、iOS模擬器或者同時使用。

在構建app時,我們將學習flutter一下內容:

  • 配置開發環境
  • 建立工程
  • Hot reload
  • 引入檔案和庫
  • 使用元件和建立自己的元件
  • 建立網路請求
  • 在列表中展示列表項
  • 新增應用主題 有也將順便學習一點Dart。

開始

Flutter開發可以在macOS,Linux或Windows上完成。 雖然您可以在Flutter工具鏈中使用任何編輯器,但IntelliJ IDEA,Android Studio和Visual Studio Code的IDE外掛可以簡化開發工作。 我們將在本教程中使用Android Studio。

配置開發環境

您可以在這找到配置flutter開發環境的說明,具體步驟因平臺而異,但主要步驟是:

  1. 下載flutter SDK,地址
  2. 將flutter的bin目錄新增到path中
  3. 執行flutter doctor命令,他會安裝flutter框架,包括dart,而且提示你任何其他需要安裝的依賴。
  4. 安裝其他依賴
  5. 在IDE中安裝flutter外掛
  6. 測試驅動應用 Flutter網站上提供的說明做得非常好,並允許您在所選平臺上輕鬆設定開發環境。 本教程的其餘部分假設您已經為Flutter開發設定了Android Studio,並且您已經解決了flutter doctor發現的任何問題。

建立工程

  • 如下所示選擇File->new->new Flutter Project
    image
  • 如下所示選擇Flutter Application,點選next.
    image
  • 如下如所示填寫專案名稱、Flutter SDK的路徑、專案儲存目錄。
    image

在Android Studio中,您會在左側看到一個顯示專案結構的皮膚。 有iOS和Android的資料夾,以及包含main.dart的lib資料夾。 您將本教程中的只在lib資料夾中工作。

image
將main.dart中的程式碼替換為以下內容:

import 'package:flutter/material.dart';

void main() => runApp(new GHFlutterApp());


class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'GHFlutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('GHFlutter'),
        ),
        body: new Center(
          child: new Text('GHFlutter'),
        ),
      ),
    );
  }
}
複製程式碼

頂部附近的main()函式使用=>運算子,為了單行函式執行應用程式。 您有一個名為GHFlutterApp的應用程式。 你在這裡看到你的應用程式本身是一個StatelessWidget(無狀態元件)。Flutter應用程式中的大多數實體都是元件,無論是無狀態的還是有狀態的。 您重寫元件的build()方法來建立您的應用的元件。 您正在使用MaterialApp 元件,該元件提供了大量遵循Material Design規範應用元件。

對於本入門教程,通過選擇它並敲擊Delete鍵,從專案中刪除test資料夾中的測試檔案widget_test.dart

你可以在macOS上,啟動iOS模擬器。 您也可以在macOS,Linux或Windows上使用Android模擬器。

Flutter 入門例項

您看到的慢速模式banner表示應用程式正在以除錯模式執行。

您可以通過單擊Android Studio視窗頂部工具欄右側的停止按鈕來停止正在執行的應用程式:

image

Hot Reload

flutter支援Hot Reload 功能。flutter中的Hot reload和Android Studio的Instant Run類似。 構建並執行應用程式,使其在模擬器或模擬器上執行:

Flutter 入門例項
現在,不停止正在執行的應用程式,將應用程式欄字串更改為其他內容:

appBar: new AppBar(
  title: new Text('GHFlutter App'),
),
複製程式碼

現在單擊工具欄上的熱重新載入按鈕:

image
在一兩秒鐘內,您會看到正在執行的應用程式中反映了變化的內容:

由於Flutter處於開發階段,熱重新載入功能可能無法正常工作,但總體而言,在構建UI時節省時間。

引入檔案

您不需要將所有的Dart程式碼儲存在一個main.dart檔案中,而是希望能夠從您建立的其他類中匯入。 現在您將看到一個用於匯入字串的示例,這將有助於您應用的本地化。 在lib資料夾中右鍵單擊並選擇New File建立一個名為strings.dart的檔案: 在新建的檔案中新增如下類:

class Strings {
  static String appTitle = "GHFlutter";
}
複製程式碼

在main.dart的頂部新增如下引入:

import 'strings.dart';
複製程式碼

如下更新應用的title:

return new MaterialApp(
  title: Strings.appTitle,
複製程式碼

更新其他的字串,使用新的字串檔案,所以最終GHFlutterApp類如下:

class GHFlutterApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
  return new MaterialApp(
    title: Strings.appTitle,
    home: new Scaffold(
      appBar: new AppBar(
        title: new Text(Strings.appTitle),
      ),
      body: new Center(
        child: new Text(Strings.appTitle),
      ),
    ),
  );
}
}
複製程式碼

構建並執行應用程式,您應該看不到任何變化,但現在您正在使用字串檔案中的字串。

元件

幾乎Flutter應用程式的每個元素都是一個元件。元件被設計成不可變的,因為使用不可變元件有助於保持應用程式UI的輕量級。

您將使用兩種基本型別的元件:

Stateless: 僅依賴於自己的配置資訊的元件,例如影象檢視中的靜態影象 Stateful: 需要維護動態資訊的元件,並通過與狀態物件互動來實現 無狀態和有狀態的元件在每個幀的Flutter應用程式中重繪,不同之處在於有狀態元件將其配置委託給狀態物件。

要開始製作自己的元件,請在main.dart的底部建立一個新類:

class GHFlutter extends StatefulWidget {
  @override
  createState() => new GHFlutterState();
}
複製程式碼

你已經建立了一個StatefulWidget子類,並且你重寫了createState()方法來建立它的狀態物件。 現在在GHFlutter上新增一個GHFlutterState類:

class GHFlutterState extends State<GHFlutter> {
}
複製程式碼

GHFlutterState使用GHFlutter的引數擴充套件狀態。

製作新元件時的主要任務是重寫在元件呈現在螢幕上時呼叫的build()方法。

重寫GHFlutterState 的build()方法:

@override
Widget build(BuildContext context) {
​    
}
複製程式碼

最終如下:

@override
Widget build(BuildContext context) {
  return new Scaffold (
    appBar: new AppBar(
      title: new Text(Strings.appTitle),
    ),
    body: new Text(Strings.appTitle),
  );
}
複製程式碼

Scaffold 是material design元件的容器。 它充當元件層次結構的根。 您已將AppBar和一個body新增到scaffold中,並且每個都包含一個Text元件

更新GHFlutterApp,以便它使用新的GHFlutter元件作為其home屬性,而不是構建預設的scaffold:

class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: Strings.appTitle,
      home: new GHFlutter(),
    );
  }
}
複製程式碼

構建並執行應用程式,您將看到新的元件正在執行:

Flutter 入門例項
現在還沒有多少變化,但是現在你已經開始構建新的元件。

網路請求

之前,您將自己的strings.dart檔案匯入到專案中。 您可以同樣匯入Flutter框架的其他庫或者Dart的庫。

例如,您現在將使用框架中的庫來進行HTTP網路呼叫,並將生成的JSON響應解析為Dart物件。 在main.dart的頂部新增兩個新的引入:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'strings.dart';
複製程式碼

您會看到當前未使用的指標在引入上。

Dart應用程式是單執行緒的,但Dart支援在其他執行緒上執行程式碼以及執行非同步程式碼,通過使用async/await模式,從而不會阻止UI執行緒。

您將進行非同步網路呼叫以檢索GitHub組織成員列表。 在GHFlutterState頂部新增一個空List作為屬性,並新增一個屬性來儲存文字樣式(_biggerFont):

var _members = [];

final _biggerFont = const TextStyle(fontSize: 18.0);
複製程式碼

名字以下劃線開頭的變數表示類的私有成員變數。 要進行非同步HTTP呼叫,請在GHFlutterState中新增一個_loadData()方法:

_loadData() async {
  String dataURL = "https://api.github.com/orgs/raywenderlich/members";
  http.Response response = await http.get(dataURL);
  setState(() {
    _members = JSON.decode(response.body);
  });
}
複製程式碼

您已將async關鍵字新增到_loadData()中,以告訴Dart它是非同步的,但是http.get()的await關鍵字表示阻塞執行。 通過dataUrl設定API的請求地址。

當HTTP呼叫完成時,您將回撥傳遞給在UI執行緒上同步執行的setState()。 這時,您解碼JSON響應並將其賦值給_members變數。 在GHFlutterState中重寫initState方法,在initState中呼叫_loadData() 方法。當元件狀態初始化時,_loadData()會被呼叫,載入資料。

@override
void initState() {
  super.initState();

  _loadData();
}
複製程式碼

使用 ListView

現在您已經有了成員列表,您需要一種方法在列表UI中顯示它們。 Dart提供了一個ListView元件,可讓您在列表中顯示資料。 ListView的作用類似於Android上的RecyclerView和iOS上的UITableView,通過重複回收利用以後的view,使列表滾動更加順滑。 在GHFlutterState中新增_buildRow()方法:

Widget _buildRow(int i) {
  return new ListTile(
    title: new Text("${_members[i]["login"]}", style: _biggerFont)
  );
}
複製程式碼

您將返回一個ListTile元件,該元件顯示第iJSON成員的"login"值,還使用您之前建立的文字樣式(_biggerFont)。

更新build方法,使body屬性值為ListView.builder:

body: new ListView.builder(
  padding: const EdgeInsets.all(16.0),
  itemCount: _members.length,
  itemBuilder: (BuildContext context, int position) {
    return _buildRow(position);
  }),
複製程式碼

您已新增padding,將itemCount設定為成員數量,設定itemBuilder呼叫_buildRow(),傳遞postion變數。

您可以嘗試熱重新載入,但可能會收到"Full restart may be required"的訊息。 如果是這樣,重啟:

Flutter 入門例項

發起網路請求,解析資料,並在列表中顯示結果是很容易的!

Flutter 入門例項

新增分割線

為了在列表中增加分割線,需要翻倍list的itemCount屬性,然後list中的position為偶數時返回Divider元件。

body: new ListView.builder(
  itemCount: _members.length * 2,
  itemBuilder: (BuildContext context, int position) {
    if (position.isOdd) return new Divider();

    final index = position ~/ 2;

    return _buildRow(index);
  }),
複製程式碼

一定要itemCount翻倍,因為增加了分割線,所以刪除padding。在itemBuilder中,返回Divider元件,或者通過整數除法計算一個新的index,利用_buildRow(index)返回一個元件。

嘗試重新載入,你應該在列表中看到分割線:

Flutter 入門例項
要在每行中新增padding,可以在_buildRow()中使用Padding元件:

Widget _buildRow(int i) {
  return new Padding(
    padding: const EdgeInsets.all(16.0),
    child: new ListTile(
      title: new Text("${_members[i]["login"]}", style: _biggerFont)
    )
  );
}
複製程式碼

ListTile現在是Padding的子部件。 熱載入以檢視行上的padding而不是分隔符上的Padding。

解析為自定義型別

在上一節中,JSON解析器將JSON響應中的每個成員都作為Dart Map型別例項新增到_members列表中,這相當於Kotlin中的Map或Swift中的Dictionary。 但是,如果你想使用自定義類。 在main.dart檔案中新增Member類:

class Member {
  final String login;

  Member(this.login) {
    if (login == null) {
      throw new ArgumentError("login of Member cannot be null. "
          "Received: '$login'");
    }
  }
}
複製程式碼

member有一個login屬性,和一個建構函式,在建構函式中當login值為null時,會丟擲一個error。 更新GHFlutterState類中的_members變數宣告,所以現在他就是一個Member物件的list

var _members = <Member>[];
複製程式碼

更新_buildRow()方法使用Member物件的login屬性而不是map的"login"key的值。

title: new Text("${_members[i].login}", style: _biggerFont)
複製程式碼

現在更新在_loadData()中的傳遞給setState()的回撥,將map解析為Member物件,新增到member物件list中。

setState(() {
  final membersJSON = JSON.decode(response.body);

  for (var memberJSON in membersJSON) {
    final member = new Member(memberJSON["login"]);
    _members.add(member);
  }
});
複製程式碼

如果你熱載入,有可能看到一個錯誤,這時你重新啟動。

下載圖片

每個member都有一個頭像url.你將新增頭像到Member類中,然後在app中顯示頭像。

在Member類中新增avatarUrl屬性,這個屬性不能為null。

class Member {
  final String login;
  final String avatarUrl;

  Member(this.login, this.avatarUrl) {
    if (login == null) {
      throw new ArgumentError("login of Member cannot be null. "
          "Received: '$login'");
    }
    if (avatarUrl == null) {
      throw new ArgumentError("avatarUrl of Member cannot be null. "
          "Received: '$avatarUrl'");
    }
  }
}
複製程式碼

更新_buildRow()方法,通過NetworkImage 和 the CircleAvatar 元件顯示頭像。

Widget _buildRow(int i) {
  return new Padding(
    padding: const EdgeInsets.all(16.0),
    child: new ListTile(
      title: new Text("${_members[i].login}", style: _biggerFont),
      leading: new CircleAvatar(
        backgroundColor: Colors.green,
        backgroundImage: new NetworkImage(_members[i].avatarUrl)
      ),
    )
  );
}
複製程式碼

設定頭像為ListTile的leading屬性,頭像將顯示在title的前面。 更新_loadData()方法,使在建立一個新的Member時使用map的"avatar_url"值。

final member = new Member(memberJSON["login"], memberJSON["avatar_url"]);
複製程式碼

構建執行,你將能看到每一個行的頭像。

Flutter 入門例項

整理程式碼

大部分程式碼現在位於main.dart檔案中。 為了使程式碼更清晰一點,你可以重構元件和你已經新增到他們自己的檔案中的類。 在lib資料夾中建立名為member.dart和ghflutter.dart的檔案。 將Member類移入member.dart,並將GHFlutterState和GHFlutter類轉移到ghflutter.dart。 您不需要member.dart中的任何匯入語句,但flutter.dart中的匯入應如下所示:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'member.dart';
import 'strings.dart';  
複製程式碼

您還需要更新main.dart中的匯入,所以整個檔案包含以下內容:

import 'package:flutter/material.dart';

import 'ghflutter.dart';
import 'strings.dart';

void main() => runApp(new GHFlutterApp());


class GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: Strings.appTitle,
      home: new GHFlutter(),
    );
  }
}  
複製程式碼

通過構建並執行應用程式,您應該看不到任何更改,但程式碼現在更清晰一些

新增主題

您可以通過將主題屬性新增到您在main.dart中建立的MaterialApp來輕鬆地將主題新增到應用程式:

lass GHFlutterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: Strings.appTitle,
      theme: new ThemeData(primaryColor: Colors.green.shade800), 
      home: new GHFlutter(),
    );
  }
}  
複製程式碼

您正在使用綠色陰影作為Material Design主題顏色值。

構建並執行應用程式,以檢視新的主題:

https://user-gold-cdn.xitu.io/2018/6/26/1643c2669c39ca8a?w=281&h=500&f=png&s=65613

大部分應用截圖都來自Android模擬器。 您也可以在iOS模擬器中執行最終的主題應用程式:

Flutter 入門例項
這就是我所說的跨平臺

最終程式碼地址

參考

www.raywenderlich.com/188257/gett…

相關文章