閱讀原文️前端
Flutter 目前仍是 Beta 3 版本,1.0 版本還在路上。不過它在 React Native/weex等跨平臺方案以外,又爲咱們提供了一種跨平臺的方案。並且其自身的許多特性,也爲咱們擴展了新的視野。若是
Fuchsia
系統最終能和 iOS、Android 成三足鼎立之式,甚至於取代 Android,那麼 Flutter 就能爲咱們帶來更多的可能。因此如今瞭解一下仍是有必要的。 本文將經過一個簡單的實例(知識小集 Flutter 版本客戶端,咱們後期會慢慢優化),同時半翻譯半參考Raywenderlich
上的 Getting Started with Flutter 這篇文章,來一步步瞭解如何使用 Flutter 構建 App。android
在這個 App 的開發過程當中,咱們將學習如下關於 Flutter 的內容:ios
在這個過程當中,咱們將同時學習一些 Dart 相關的知識。項目的完整代碼在 Github 上能夠找到。git
咱們能夠在 macOS
、Linux
或者 Windows
上開發 Flutter 應用。目前 Flutter 團隊爲一些 IDE 開發了相應的插件,這些 IDE 包括 IntelliJ IDEA
、Android Studio
和 Visual Studio Code
。個人開發環境主要爲 macOS + Visual Studio Code,因此本文主要基這二者來進行描述。github
實際的配置過程能夠參考官方文檔 Get Started: Install on macOS。具體的步驟各個平臺稍有不一樣,但主要是如下幾步:macos
git
庫;bin
目錄到咱們指定的目錄;flutter doctor
命令,這個命令將告訴咱們缺乏哪些依賴;須要注意的是,若是想在 iOS 模擬器或 iOS 設備上構建和測試應用,咱們須要使用 macOS 系統,同時須要安裝
Xcode 9.0+
。json
在安裝了 Flutter 插件的 VS Code 中,咱們能夠經過 View > Command Palette...
或者快捷鍵 cmd+shift+p
來打開 命令面板(command palette),而後輸入 Flutter:New Project
並回車:小程序
爲工程取名爲 awesome_tips_flutter
,並回車。選擇一個目錄來存儲工程,而後等待 Flutter 配置好工程。配置的過程主要有幾個步驟:api
flutter packages get
命令來獲取依賴包;flutter doctor
命令來檢測依賴包;如圖是構建過程的部分信息:xcode
工程建立完成後,IDE 會默認打開 lib
目錄下的 main.dart
文件,這也是咱們 App 的入口。
注意:從 Flutter Beta 3 開始,建立 Widget 時,
new
關鍵字是可選的。目前我這生成的模板代碼部分仍是帶 new 關鍵字的。
在左側的工程目錄中,咱們能夠看到 ios
、android
、lib
這些目錄,lib 目錄下的代碼將應用於兩個平臺,目前咱們也主要是在這個目錄下工做。
爲了構建咱們本身的應用,先刪除 main.dart 中現有的代碼,並用以下代碼替代:
import 'package:flutter/material.dart';
void main() => runApp(new AwesomeTips());
class AwesomeTips extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Awesome Tips',
home: Scaffold(
appBar: AppBar(title: Text('Awesome Tips')),
body: Center(
child: Text('Awesome Tips'),
)
)
);
}
}
複製代碼
頂部的 main() 函數使用 =>
操做符來指定單行函數的函數體(相似於 ES6 中的箭頭函數),並運行 App。runApp
的參數是咱們的 AwesomeTipsApp
類(根 Widget)。
在這裏,咱們的 AwesomeTipsApp 類繼承自 StatelessWidget
。Flutter 中大部分實體都是 Widget,或者是無狀態的(stateless),或者是有狀態的(stateful)。咱們重寫 Widget 的 build()
方法來構建自定義的 App Widget。
咱們先來運行一下這個 App。首先啓動 iOS 模擬器。選擇菜單 Debug -> Start Debugging
構建並運行工程。能夠看到 VS Code 打開了 Debug Console
(調試控制檯) 面板,同時 xcode-builder
開始構建並啓動 App。初始效果以下圖:
同時,咱們能夠在 VS Code 頂部看到一個調試工具欄,咱們能夠經過這個工具欄來中止或者從新加載 App。
Flutter 開發最吸引人的一個方面就是當程序代碼更改時,能夠自動執行 Hot Reload
操做,來從新加載 App。咱們來試試這個特性,對咱們的程序作個小小的修改:
appBar: AppBar(title: Text('Awesome Tips for Test')),
複製代碼
在咱們保存文件時,VS Code 會自動啓動 Hot Reload 功能,加載完成後,模擬器會顯示新的內容。固然咱們也能夠手動點擊調試工具欄上的 Hot Reload 按鈕來啓動熱加載。來看看效果。
注:因爲 Flutter 仍是 Beta 版,因此 Hot Reload 並不老是能正常工具。我就遇到了相似
Request to Dart VM Service timed out: _flutter.listViews({})
這樣的問題,解決方法是重啓 Debug。
一般咱們都不但願在一個文件中放入大量的代碼,而是將代碼分散在不一樣的文件中,並經過必定的方式將這些文件組織起來。而後若是一個文件須要用到其它文件的類或方法,只須要導入相關文件便可。在一個 Dart 文件中,咱們能夠經過 import
關鍵字來實現這一目標。
好比上面代碼中,咱們但願將字符串統一放在一個文件中來管理,那麼能夠建立一個 strings.dart
文件。在 lib 目錄處點擊右鍵,會彈出菜單,選擇 New File
,並輸入文件名。
在 string.dart
中添加如下代碼:
class Strings {
static String appTitle = "Awesome Tips";
}
複製代碼
而後在 main.dart
中經過如下方式導入:
import 'strings.dart';
複製代碼
如今就能夠在 AwesomeTipsApp 中使用 appTitle
了:
class AwesomeTipsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Strings.appTitle,
home: Scaffold(
appBar: AppBar(title: Text(Strings.appTitle)),
body: Center(
child: Text(Strings.appTitle),
)
)
);
}
}
複製代碼
在 Flutter App 中,幾乎全部的界面元素都是 Widget。Widget 被設計成是不可變的(immutable),由於這樣可讓 App 的 UI 輕量化。咱們可使用兩種類型的 Widget:
State
對象交互。兩種類型的 Widget 都會在 Flutter App 的每一幀進行重繪,不一樣的是 Stateful Widgets 會將其配置交給 State 對象來管理。關於 Flutter 界面開發,能夠參考阿里閒魚團隊 的**《深刻了解Flutter界面開發》**一文。
咱們如今來建立一個 Widget 展現列表。在 lib 目錄中新建文件 content_list.dart
,在文件中加入以下代碼:
import 'package:flutter/material.dart';
class ContentList extends StatefulWidget {
@override
createState() => _ContentListState();
}
複製代碼
這裏咱們建立了 StatefulWidget
的一個子類 ContentList
並重寫了 createState()
方法,該方法返回 ContentList 對應的 State 對象。而後咱們在同一文件中添加如下代碼:
class _ContentListState extends State<ContentList> {
}
複製代碼
_ContentListState
繼承自泛型參數爲 ContentList 的 State 對象。在 _ContentListState 中,咱們的主要工做就是重寫 build()
方法,這個方法在 Widget 被渲染到屏幕上時會調用。目前咱們尚未涉及到數據的處理,因此暫時和以前同樣,在 ContentList 中顯示一個簡單的文本。在 build() 方法中添加如下代碼:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Strings.appTitle)),
body: Text(Strings.appTitle),
);
}
複製代碼
Scaffold
類是Material Design Widgets
的容器。它一般做爲 Widget 層級的根。
上面的代碼咱們添加了一個 AppBar 和一個 body 到 Scaffold 中。接下來咱們用這個 ContentList Widget 替換 main.dart 中的 home
屬性的內容:
import 'content_list.dart';
void main() => runApp(AwesomeTipsApp());
class AwesomeTipsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Strings.appTitle,
home: ContentList(), // 替換此處內容
);
}
}
複製代碼
編譯運行程序,獲得的結果和上面差很少。
咱們最終要展現的是知識小集的內容清單,因此須要從服務器上獲取到清單內容,並轉換成咱們須要的 Dart 對象。這裏咱們須要用到兩個庫:
package:http/http.dart
:負責網絡請求,從服務端獲取數據;dart:convert
:將服務端返回的字符串轉換成 JSON
對象;咱們在 main.dart 中導入這兩個模塊:
import 'package:http/http.dart';
import 'dart:convert';
複製代碼
須要注意的是:Dart 應用是單線程的,可是 Dart 支持代碼運行在其它線程上,同時也支持使用
async/await
模式讓代碼異步執行,而不會阻塞 UI 線程。
接下來咱們須要經過異步網絡調用來獲取知識小集的內容列表。首先咱們在 _ContentListState 類的頂部添加一個空列表屬性,用於保存內容清單:
var _items = [];
複製代碼
Dart 語言中,若是屬性/方法名是以_開頭,則表示這個屬性/方法是類私有的。
而後添加一個 _loadData() 方法,咱們在這作網絡請求:
void _loadData() async {
String dataURL =
"https://app.kangzubin.com/iostips/api/feed/list?page=1&from=flutter-app&version=1.0";
http.Response response = await http.get(dataURL);
// ...
}
複製代碼
這裏咱們在 _loadData() 後面加上 async
關鍵字,用於告訴 Dart 這是一個異步方法,同時在 http.get
前使用 await
關鍵字,來阻塞後面的代碼執行。當 HTTP 調用完成後,服務端返回的是一個 JSON
字符串,具體結構以下:
{
"code": 0,
"msg": "SUCCESS",
"data": {}
}
複製代碼
對於 feed/list
接口,其 data
中的結構以下:
"data": {
"feeds": [{
"fid": "96",
"auther": "halohily",
"title": "如何重寫自定義對象的 hash 方法",
"url": "https://weibo.com/3656155132/GfEGebnEN",
"platform": "0",
"postdate": "2018-05-08"
}, {
"fid": "95",
"auther": "南峯子",
"title": "微博一週推送",
"url": "https://weibo.com/3321824014/GfviNzT3z",
"platform": "0",
"postdate": "2018-05-07"
}]
}
複製代碼
在獲取到 JSON 字符串後,咱們首先須要將其轉換成 JSON 對象,而後根據 code 是否爲 0 作處理。若是請求成功,則須要從 data
中取出 feeds
的數據。同時,咱們但願將 feed 數據轉換成一個 Dart 對象,因此咱們建立一個 feed.dart
文件,並添加以下代碼:
class Feed {
final String author;
final String title;
final String postdate;
Feed(this.author, this.title, this.postdate);
}
複製代碼
而後咱們就能夠對返回的數據作處理,將每一條 feed 轉換成一個 Feed
對象,並存儲在 _items 中。完整的 _loadData() 代碼以下所示:
void _loadData() async {
String dataURL =
"https://app.kangzubin.com/iostips/api/feed/list?page=1&from=flutter-app&version=1.0";
http.Response response = await http.get(dataURL);
final body = JSON.decode(response.body);
final int code = body["code"];
if (code == 0) {
final feeds = body["data"]["feeds"];
var items = [];
feeds.forEach((item) =>
items.add(Feed(item["author"], item["title"], item["postdate"])));
setState(() {
_items = items;
});
}
}
複製代碼
若是咱們但願在狀態改變時,觸發界面從新渲染,則須要調用 setState() 方法來設置咱們的屬性值。
有了加載數據的方法,咱們就須要在合適的位置來調用。咱們暫且在 _ContentListState 類中重寫 State 的 initState()
方法,以下所示:
@override
void initState() {
super.initState();
_loadData();
}
複製代碼
Widget 生命週期相關的內容,咱們有機會再講。
至此,咱們已經有了列表數據,接下來就須要將數據顯示在界面上了。Flutter 提供了 ListView
Widget 來顯示一個列表,這個 Widget 能很流暢地展現列表內容。
咱們先在 _ContentListState 類中添加一個私有方法 _buildRow()
,以建立顯示單元格的 widget:
Widget _buildRow(int i) {
Feed feed = this._items[i];
return ListTile(
title: Text(
feed.title,
overflow: TextOverflow.fade,
),
subtitle: Text(
'${feed.postdate} @${feed.author}',
));
}
複製代碼
咱們暫且返回一個 ListTile
來顯示內容的標題及發佈日期和做者。接下來咱們修改 build()
方法中 Scaffold 的 body:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(Strings.appTitle)),
body: new ListView.builder(
padding: const EdgeInsets.all(13.0),
itemCount: _items.length * 2,
itemBuilder: (BuildContext context, int position) {
// 此處爲添加分割線
if (position.isOdd) return Divider();
final index = position ~/ 2;
return _buildRow(index);
},
),
);
}
複製代碼
在這段代碼中,咱們經過 ListView.builder
來建立一個 ListView,並經過參數來配置列表的顯示。這裏咱們沒有處理單元格點擊等事件,後續咱們會作改進。
OK,保存代碼,Hot Reload 後的效果以下:
很簡單吧?這樣,咱們的任務基本完成。
這裏咱們只是獲取了第1頁的數據,分頁處理後續再完善。
最後咱們來看看如何爲 App 添加主題。能夠說這很容易,只須要設置 main.dart 中 MaterialApp 的 theme
屬性,咱們來試試:
class AwesomeTipsApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: Strings.appTitle,
theme: ThemeData(primaryColor: Colors.red.shade800),
home: ContentList(),
);
}
}
複製代碼
咱們使用了 Material Design
顏色值來設置主題顏色,效果以下:
在本文中,咱們經過一個簡單的例子來了解了一下若是使用 Flutter 來構建 App,能夠在 awesome-tips-flutter-app 下載完整的示例代碼。固然,構建一個完整的 App 還須要作不少事情,還有許多技術學習。後期咱們會逐步來完善這個 App,並讓其達到上線的標準,最終發佈到應用市場上。
爲了更方便你們獲取 Flutter 相關的開發資源,咱們在 Github 上開了一個 repo flutter-resources,歡迎你們一塊兒來維護這個 repo。
知識小集是一個團隊公衆號,主要定位在移動開發領域,分享移動開發技術,包括 iOS、Android、小程序、移動前端、React Native、weex 等。每週都會有 原創 文章分享,咱們的文章都會在公衆號首發。歡迎關注查看更多內容。