還在學iOS?是時候學習Flutter了(二)

概述

本文承接上文,是Flutter For iOS 的第二篇文章,經過閱讀本文你將獲取以下信息:html

  • 線程和異步
  • 項目結構與本地化
  • 視圖控制器
  • 佈局
  • 手勢
  • 表單
  • 列表
  • 其餘

線程和異步

如何寫異步代碼

Dart擁有單線程執行模型,同時也支Isolate (一種將Dart代碼執行在另外一個線程的方式)、事件循環和異步編程。除非你建立一個Isolate ,你的Dart代碼將一直在主UI線程中執行,並由事件循環驅動。Flutter的事件循環至關於iOS中的主循環,也就是說Looper 綁定在主線程上。ios

Dart的單線程模型並不意味着你必須將一切代碼做爲一個致使UI卡頓的阻塞塊來執行。相反,你可使用Dart提供的異步功能好比說:async/awiat 來執行異步任務。數據庫

好比說,你可使用asyn/await執行網絡代碼和繁重的工做而避免UI卡頓。編程

一旦網絡請求結束,經過調用setState()更新UI,觸發當前widget的子樹和更新數據。json

下面例子異步加載數據並展現在ListViews上:api

參考下一節瞭解如何在後臺線程執行任務,與iOS有何不一樣。bash

如何將任務放到後臺線程

因爲Flutter的單線程模型和事件循環,你不用擔憂線程管理或者開啓後臺線程。你能夠放心的使用async/await方法執行I/O操做,好比訪問磁環或者請求網絡。另外一方面,如何你想執行復雜的計算而使CPU持續的處於繁忙狀態,你能夠將任務已到Isolate而避免阻塞事件循環。微信

對於iOS操做,將方法聲明爲async方法,使用await等待耗時任務完成。網絡

loadData() async {
  String dataURL = "https://jsonplaceholder.typicode.com/posts";
  http.Response response = await http.get(dataURL);
  setState(() {
    widgets = json.decode(response.body);
  });
}
複製代碼

這是對常的I/O操做如網絡請求,訪問數據庫的常規操做。app

可是,當你處理大量數據的時候這仍然可能會致使UI掛起。在Flutter中,使用Isolate 來使用CPU多核的優點來執行耗時任務或者計算密集型任務。

Isolates 是分離線程,它不和主線程共享任何堆內存,這也就意味着,你不能訪問主線程中的變臉,或者直接調用setState()更新主線程。Isolates正如其名,不能共享內存。

下面代碼展現了一個簡單的isolate, 如何將數據返回到主線程並更新UI的。

上面代碼中,dataLoader()Isolate,它在一個獨立的線程中執行。在這個isolate中你能夠執行CPU密集型任務如解析JSON,或者執行浮躁的數學計算任務,如加密或者信號處理。

你能夠執行完整代碼,以下:

如何發生網絡請求

在Flutter中使用流行的第三方庫http package 來請求網絡是很是簡單的。它抽象了大量的本須要你本身實現的操做,使得發送請求很是簡單。

爲了使用http這個框架,你須要在pubspec.yaml中增長依賴。

dependencies:
  ...
  http: ^0.11.3+16
複製代碼

爲了發起網絡請求,在async方法http.get() 前添加await

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
[...]
  loadData() async {
    String dataURL = "https://jsonplaceholder.typicode.com/posts";
    http.Response response = await http.get(dataURL);
    setState(() {
      widgets = json.decode(response.body);
    });
  }
}
複製代碼

如何展現耗時任務的進度

在iOS中,當在後臺執行一個耗時任務的時候,你經過會使用UIProgressView展現進度。

在Flutter中,使用ProgressIndicator 組件。經過給它傳遞一個布爾標識來控制它的展現,告訴Flutter去更新它的狀態在耗時任務執行以前和執行結束以後隱藏掉它。

在下面的例子中,build方法被分割爲三個不一樣方法。若是showLoadingDialog()是true,那就渲染ProgressIndicator 不然使用網絡返回的數據渲染ListView

項目結構、本地化、依賴和資源管理

如何在Flutter中管理圖片,如何放置多種分辨率的圖片

與iOS將圖片和資源做爲不一樣的類型來處理不一樣的是Flutter中只有一種assets。iOS中資源被放在Image.xcassert中文件中,而Flutter中放在assets文件中。與iOS同樣,assets是許多類型的文件,不只僅是圖片,好比說你能夠將json文件放到my-assets文件夾中。

my-assets/data.json
複製代碼

pubspec.yaml文件中聲明:

assets:
	- my-assets/data.json
複製代碼

而後就能夠在代碼中使用AssetBunlde訪問:

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;

Future<String> loadAsset() async {
  return await rootBundle.loadString('my-assets/data.json');
}
複製代碼

對於圖片,Flutter和iOS的格式同樣,圖片能夠是1倍圖,2倍圖,3倍圖或者其餘任何倍數。這些所謂的 devicePixelRatio 表示的是物理像素到單個邏輯像素的比率。

Assets能夠被放到任何類型的文件夾中,Flutter中沒有事先預約義文件的結構。在pubSpec.yaml文件中聲明assets,而後Flutter就能識別出來。

好比說:將my_icon.png放置到Flutter項目中,你可能把存儲的文件夾叫做images。把相關係數的圖片放在不一樣的子文件家中,以下:a

images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image
複製代碼

接下來在pubspec.yaml中聲明圖片

assets:
	- images/my_icon.png
複製代碼

你如今就可使用AssetImage返回圖片

return AssetImage("images/a_dot_burr.jpeg");
複製代碼

或者直接使用Image組件

@override
Widget build(BuildContext context) {
  return Image.asset("images/my_image.png");
}
複製代碼

更多細節參考Adding Assets and Images in Flutter

如何存放字符串,如何管理本地化

iOS中,咱們使用Localizable.strings文件管理本地化字符串,而Flutter中沒有專門的模塊處理本地化字符串,因此最好的辦法就是將字符串統一放到一個類中,以靜態字段的形式存儲。以下:

class Strings {
  static String welcomeMessage = "Welcome To Flutter";
}
複製代碼

訪問方式以下:

Text(Strings.welcomeMessage)
複製代碼

默認狀況下,Flutter只支持英文字符串,若是你想支持其餘語言,能夠經過引入flutter_localizations庫。 同時你須要將Dart的intl包以便支持 i10n 機制,好比日期/時間格式化。

dependencies:
  # ...
  flutter_localizations:
    sdk: flutter
  intl: "^0.15.6"
複製代碼

爲了使用flutter_localizations ,須要在App widget上指定 localizationsDelegatessupportedLocales 屬性。

import 'package:flutter_localizations/flutter_localizations.dart';

MaterialApp(
 localizationsDelegates: [
   // Add app-specific localization delegate[s] here
   GlobalMaterialLocalizations.delegate,
   GlobalWidgetsLocalizations.delegate,
 ],
 supportedLocales: [
    const Locale('en', 'US'), // English
    const Locale('he', 'IL'), // Hebrew
    // ... other locales the app supports
  ],
  // ...
)
複製代碼

代理中包含了實際的本地化值,supportedLocales定義了要支持那些語言的本地化。上面的例子使用的是MaterialApp, 它既有針對基本Widget的本地化值GlobalWidgetsLocalizations,也有針對Material widget的MaterialWidgetsLocalizations本地化。若是你的App使用的是WidgetApp,那麼後者就不須要了。值得注意的是這兩個代理都包含默認值,但若是你想讓你的App本地化,你扔須要提供一個或者多個代理做爲你的App本地化副本。

當初始化完成的時候,WidgetsApp或者MaterialApp使用你指定的代理爲你建立了一個Localizationswidget。你可從LocalizationsWidget中隨時訪問當前設備的本地化信息,或者使用window.locale

爲了訪問本地化資源,使用Localizations.of()方法訪問有給定的delegate提供的特有的本地化類。使用intl_translation取出翻譯副本到 arb 文件中。將它們引入App中,並用intl來使用它們。

更多國際化和本地化的內容參考: internationalization guide,它包含了不使用intl示例代碼。

須要注意的是:Flutter1.0 beta2 以前 fullter中定義的資源文件不能被原生訪問,同時原生定義的資源不能被flutter訪問,由於它們存儲在不能的文件目錄下。

如何管理依賴

在iOS中,咱們將依賴添加到Podfile文件中,Flutter使用的是Dart語言構建的系統和Pub包管理器操做依賴。這些工具將原生 Android 和 iOS 包裝應用程序的構建委派給相應的構建系統。

若是在你的Flutter項目中iOS目錄下包含Podfile,只須要使用它添加iOS原生的依賴。使用 pubspec.yaml 聲明Flutter 中的外部依賴。 Pub網站能夠找到一些比較好用的第三方依賴。

視圖控制器

Flutter中與ViewControllers相等的元素是什麼?

在iOS中,ViewController表示用戶界面的一部分,一般表示一個屏幕或者部分屏幕。多個ViewController組合在一塊兒構造複雜的用戶界面,並幫助你規整應用的UI部分。在Flutter中,這項工做落在了Widget頭上,正如導航那一個章節提到的,屏幕由Widget所表示,因"一切都是Widget"。使用Navigator在不一樣的路由間切換表示不一樣的屏幕或者頁面或者表示不一樣的狀態或者渲染相同的數據。

如何監聽iOS的生命週期事件

在iOS中,你能夠重寫ViewController中的方法來捕獲視圖的生命週期,或者在AppDelegate中註冊生命週期的回調。在Flutter中沒有這兩個概念,可是咱們能夠經過hookWidgetsBinding並在didChangeAppLifecycleState()方法中監聽生命週期事件。

可以監聽到的生命週期事件以下:

  • Inactive — 應用程序處於不活躍狀態,不能相應用戶輸入。該事件只在iOS中有效。
  • paused — 應用程序當前不可用,不響應用戶輸入,可是還在後臺運行。
  • resumed — 應用程序可用,並能響應用戶輸入。
  • suspending — 應用程序暫時被掛起。該事件只在Android系統上有效。

更多細節參考:AppLifecycleStatus documentation

佈局

Flutter中的UITableViewUICollectionView

Flutter中使用ListView實現iOS中的UITableViewUICollectionView。實現代碼以下:

如何知道那個cell被點擊

在iOS中,經過實現 tableView:didSelectRowAtIndexPath:方法來相應cell的點擊事件,在Flutter中,使用所包含的widget自己提供的事件來處理相應。

如何動態更新ListView

在iOS中,咱們使用reloadData來刷新表格視圖。

在Flutter中,若是更新setState()中的小部件列表,你會發現列表數據沒有發生變化。這是由於當調用setState()時,Flutter呈現引擎會查看widget樹以查看是否有任何更改。當它到達ListView時,它執行==檢查,並肯定兩個ListView是相同的。沒有任何改變,所以不須要更新。

在setState()方法內建立一個新List是更新ListView的一個簡單的方法。並將舊列表中的數據複製到新列表中。雖然這種方法很簡單,但不建議用於大型數據集,以下一個示例所示。

咱們推薦使用ListView.Builder來構建列表,它比較高效。當你的列表包含大量數據的列表時,此方法很是有用。

與建立一個ListView不一樣的是,建立ListView.builder 攜帶兩個參數:列表的初始長度和ItemBuilder方法。

ItemBuilder方法和iOS中的table或者collection的cellForItemAt代理類似,同樣的攜帶一個位置,並返回該位置須要渲染的cell。

最後也是最重要的,onTap方法並無從新建立一個list,而是.add了一個Widget。

如何使用相似ScrollView的功能

@override
Widget build(BuildContext context) {
  return ListView(
    children: <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}
複製代碼

手勢檢測和觸摸事件處理

如何向widget添加一個事件監聽

若是widget支持事件處理,如RaisedButton,能夠直接將相應方法傳遞給對應的屬性,如RaisedButton的onPressed。

Widget build(BuildContext context) {
  return RaisedButton(
    onPressed: () {
      print("click");
    },
    child: Text("Button"),
  );
}
複製代碼

若是widget不支持事件處理,可使用GestureDetector包裹一下,而後給onTap屬性傳遞一個方法。

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Sample App'),
    ),
    body: Center(
      child: GestureDetector(
        child: FlutterLogo(
          size: 200,
        ),
        onTap: () {
          print('taped');
        },
      ),
    ));
}
複製代碼

如何處理widget上的其餘類型的事件

咱們可使用 GestureDetector 來實現以下事件的監聽:

  • 單擊
    • onTapDown — 按下手勢事件
    • onTapUp — 擡起事件
    • onTap — 點擊事件
    • onTapCancel — 取消點擊事件,onTapDown發生,但onTap沒有發生。
  • 雙擊
    • onDoubleTap — 雙擊事件
  • 長按
    • onLongPress — 長按事件
  • 垂直拖動
    • onVerticalDragStart —開始垂直移動
    • onVerticalDragUpdate — 垂直移動進行中。
    • onVerticalDragEnd — 垂直移動結束。
  • 水平拖動
    • onHorizontalDragStart — 開始水平移動。
    • onHorizontalDragUpdate — 水平移動進行中。
    • onHorizontalDragEnd — 水平移動結束。

下面代碼展現了使用 GestureDetector 實現雙擊事件:

運行效果:

主題和文本

如何爲應用程序設置主題

Flutter提供了一套完美符合Material Design的主題,它幫你處理了大多數須要你本身處理的樣式和主題。

爲了在你的App中充分發揮Material組件的優點,在頂層組件上聲明MaterialApp,做爲你的應用的入口。MaterialApp 是一個便利的組件,它包含了許多App一般須要的Materail Desigin風格的組件。它經過由給WidgetsApp增長MD功能實現的。

同時 Flutter 足夠地靈活和富有表現力來實現任何其餘的設計語言。在 iOS 上,你能夠用 Cupertino library 來製做遵照 Human Interface Guidelines 的界面。查看這些 widget 的集合,請參閱 Cupertino widgets gallery

你也能夠在你的 App 中使用 WidgetApp,它提供了許多類似的功能,但不如 MaterialApp那樣豐富。

對任何子組件定義顏色和樣式,能夠給 MaterialApp widget 傳遞一個 ThemeData 對象。舉個例子,在下面的代碼中,primary swatch 被設置爲藍色,而且文字的選中顏色是紅色:

class SampleApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textSelectionColor: Colors.red
      ),
      home: SampleAppPage(),
    );
  }
}
複製代碼

如何在Text widget上使用自定義字體

在 iOS 中,你在項目中引入任意的 ttf 文件,並在 info.plist 中設置引用。在 Flutter 中,在文件夾中放置字體文件,並在 pubspec.yaml 中引用它,就像添加圖片那樣。

fonts:
   - family: MyCustomFont
     fonts:
       - asset: fonts/MyCustomFont.ttf
       - style: italic
複製代碼

而後在你的 Text widget 中指定字體:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Sample App"),
    ),
    body: Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}
複製代碼

如何設置Text widget的樣式

除了字體之外,你也能夠給 Text widget 的樣式元素設置自定義值。Text widget 接受一個 TextStyle 對象,你能夠指定許多參數,以下:

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

表單輸入

表單在Flutter中如何工做的,如何取回用戶輸入的值

在iOS中,咱們一般在用戶提交的時候獲取組件上的內容,對於具備使用獨立狀態的不可變組件的Flutter來說,你可能會好奇如何獲取用戶輸入內容。

對於表單操做而言,與其餘功能同樣也是經過特定的Widget實現的。經過使用 TextField或者TextFormField 能夠經過 TextEditingController 取回輸入內容。

示例代碼以下:

運行效果

更多信息參考: Flutter CookbookRetrieve the value of a text field

如何實現相似文本輸入框佔位符的功能

經過給decoration屬性傳遞一個InputDecoration對象來給TextField實現佔位符的功能。

body: Center(
  child: TextField(
    decoration: InputDecoration(hintText: "This is a hint"),
  ),
)
複製代碼

如何展現驗收錯誤信息

與上面代碼同樣,只不過是再添加一個errorText字段,經過state控制錯誤信息的提示。

示例代碼以下:

運行效果:

與硬件、第三方服務和平臺的交互

如何與平臺和平臺原生代碼交互

Flutter不是在直接在平臺下運行代碼的,相反,由Dart語言構建的FlutterApp在設備本機運行,"迴避"平臺提供的SDK。好比說:在Dart中發送一個網絡請求,它是直接在Dart上下文中執行的,而不適用咱們在寫原生App的時候所使用的Android或者iOSAPI。咱們的FlutterApp仍然被原生app的ViewController當作一個View所持有,但咱們不用直接訪問ViewController或者原生框架。

這並不意味着Flutter應用不能與原生API或者其餘你寫的原生代碼交互。Flutter提供了 platform channels,它能夠與持有你Flutter視圖的VIewController通訊或者交換數據。platform channels 本質上是一個異步通訊機制,橋接了Dart代碼和其宿主ViewController,iOS框架。好比說。你能夠用platform channels執行一個原生的函數,或者是從設備的傳感器中獲取數據。

除了直接使用platform channels以外,你還可使用一系列預先製做好的 plugins。例如,你能夠直接使用插件來訪問相機膠捲或是設備的攝像頭,而沒必要編寫你本身的集成層代碼。你能夠在 Pub 上找到插件,這是一個 Dart 和 Flutter 的開源包倉庫。其中一些包可能會支持集成 iOS 或 Android,或二者都可。

若是你在 Pub 上找不到符合你需求的插件,你能夠本身編寫 ,而且發佈在 Pub 上

如何訪問GPS傳感器

使用 geolocator

如何訪問相機

使用 image_picker

如何使用FaceBook登錄

使用 flutter_facebook_login

如何使用Firebase

大多數 Firebase 特性被 first party plugins 包含了。這些第一方插件由 Flutter 團隊維護:

如何建立原生集成層代碼

若是有一些 Flutter 和社區插件遺漏的平臺相關的特性,能夠根據 developing packages and plugins 頁面構建本身的插件。 Flutter 的插件結構,簡要來講,就像 Android 中的 Event bus。你發送一個消息,並讓接受者處理並反饋結果給你。在這種狀況下,接受者就是在 Android 或 iOS 上的原生代碼。

數據庫和本地存儲

如何在Flutter中使用UserDefaults

在iOS中,咱們可使用UserDefaults 來存儲鍵值對集合,在Flutter中,可使用 Shared Preferences plugin插件來顯示相似的功能。 這個插件包裝了UserDefaults和Android 上的 SharedPreferences

Flutter中和Coredata相等的功能。

可使用 SQFlite 插件實現iOS中CoreData相關的功能。

通知

如何設置推送通知

在iOS,你須要在開發者網站上註冊app以便獲取推送權限。在Flutter中使用firebase_messaging 插件能夠實現推送。 更多關於使用Firebase Cloud Messaging API的文檔請參考: firebase_messaging

參考

本文主要參考Flutter官方文檔,Flutter中文網。 因爲排版緣由,文中我使用了圖片的形式展現代碼,若是你須要源碼,能夠關注個人公衆號,回覆關鍵字"flutter"獲取相關代碼。

本文首發自微信公衆號:RiverLi

相關文章
相關標籤/搜索