文章概述
本人之前主要從事iOS開發工作,剛好Flutter文件中有一篇Flutter for iOS developers的文件,之前兩篇文章,我們大致上體驗了Flutter,這篇文中我將從iOS開發者的角度來學習Flutter,與官方文件不同的是,這篇文章會更注重實踐。由於文件很長,我將用兩篇文章講解。這是第一篇。通過閱讀本篇文章,你講學習到如下內容:
- Widget與UIView的區別。
- 導航,如何在頁面間跳轉
- 執行緒與非同步
Widget與UIView
對於我們iOS開發者來講,UIView
再熟悉不過,它是我們構建介面的必備元素。而在Flutter中,我們可以將Widget
看做是UIView
,但它與UIView
並不是完全等價的。
對於Widget而言,它是不可變的,當Widget所描述的介面需要改變的時候,Flutter是重新構建一個Widget實現的。對於UIView而言,介面改變的時候並不是去重新繪製(除非呼叫setNeedsDisplay()方法),而是屬性的改變。
更確切的說,Widget更輕量級,因為它是不可變的。它只是UI的描述,是對下層真實檢視物件的描述,而不是檢視本身,也不會去繪製任何東西。
Flutter包含 Material Components元件庫。這是一個遵循 Material Design guidelines 規範實現的控制元件,這是一個非常靈活的設計系統,並針對所有的系統進行了優化,包含iOS系統。
我們可以使用 Cupertino widgets 元件來實現遵循 Apple’s iOS design language 的介面。
如何更新Widgets
Flutter中更新widgets需要去操作它的state,不向UIView一樣,直接修改物件的屬性即可。Flutter中包含有狀態Widgets和無狀態Widgets,分別用StatefulWidget
和 StatelessWidget
表示。
舉例來說:如果你只需要展示一個圖示,並且它不會被改變,這時使用StatelessWidget
即可完成,但如果你需要根據網路請求返回的結果來動態的設定圖片就需要使用StatefulWidget
來完成了。
StatefulWidget
和 StatelessWidget
的最大區別是,StatefulWidget
擁有一個儲存狀態資料的State
物件,並且在整個Widget Tree構建的過程中一直攜帶者它,永不丟失。
有一個簡單的記法:如果一個Widget在它的build
方法之外改變,它就是有狀態的,如果在第一次構建之後永不改變,它就是無狀態的。一個有狀態的Widget的父控制元件可以是無狀態的,只要父控制元件不受子控制元件狀態的影響即可。如Text
就是一個無狀態的Widget元件。
// class Text extends StatelessWidget
Text(
'I like Flutter!',
style: TextStyle(fontWeight: FontWeight.bold),
);
複製程式碼
不難發現Text沒有攜帶任何狀態資訊,只有通過建構函式傳遞的資訊。
如果要實現通過點選事件來改變Text的文字資訊,需要通過將Text用StatefulWidget包裹一下來實現,
程式碼如下:
效果如下:
如何對Widget佈局
In iOS, you might use a Storyboard file to organize your views and set constraints, or you might set your constraints programmatically in your view controllers. In Flutter, declare your layout in code by composing a widget tree.
The following example shows how to display a simple widget with padding:
在編寫iOS程式碼的時候,你可以用Storyboard來構建檢視和設定約束,或者在viewContoller中編寫約束程式碼。在Flutter中,我們將佈局程式碼寫在Widget樹種。
下面的例子展示了一個使用padding的Widget:
程式碼效果如下:
![佈局效果圖.jpg](https://test.demo-1s.com/images/2019/05/18/kqr78c99LPRXDpAz.jpg)
你可以對任何一個Widget使用padding屬性,它模擬了iOS中的約束功能。
widget layout這篇文章詳細介紹了Flutter所提供的佈局功能。
如何從佈局中新增或者刪除一個元件
In iOS, you call addSubview()
on the parent, or removeFromSuperview()
on a child view to dynamically add or remove child views. In Flutter, because widgets are immutable there is no direct equivalent to addSubview()
. Instead, you can pass a function to the parent that returns a widget, and control that child’s creation with a boolean flag.
The following example shows how to toggle between two widgets when the user clicks the FloatingActionButton
:
在iOS中,我們可以呼叫父檢視的addSubview()
方法為父檢視新增可以子檢視,或者呼叫子檢視的removeFromSuperview()
方法將自身從其父檢視中移除,通過以上兩個方法可以動態的新增或者移除子檢視。在Flutter中,由於widget是不可變的,沒有與addSubvie()
等價的功能函式。在flutter中可以使用一個bool型變數來控制子檢視是否需要建立。
來看下面的例子:通過一個toggle變數來控制子檢視顯示的內容:
效果如下:
如何設定Widget 動畫
在iOS中我們可以使用animate(withDuration:animations:)
方法為一個view設定動畫,在Flutter中需要使用第三方庫來包裝widget而不是使用具體動畫屬性的widge。
在Flutter中,使用AnimationController
它是Animation<double>
型別,可以控制一個動畫的終止,執行,暫停和反轉。它需要一個Ticker
,當垂直同步訊號時產生在它執行的每一幀都產生一個0到1之間的線性插值。你可以建立一個或者多個動畫繫結到這個controller上。
舉個例子:
你可能使用CurvedAnimation
依據插值曲線來完成一個動畫,在這種場景下,controller是動畫執行的"主人",CurvedAnimation
計算用來計算的曲線會代替controller預設線性模式。
當構建widget樹你將Animation
賦值給一個widget的動畫屬性,比如,FadeTransition
的不透明度,然後告訴controller開始執行動畫。
下面的例子展示瞭如何寫一個淡出動畫,當點點選了按鈕之後,logo會淡出顯示。
效果如下:
更多動畫相關的資料可以參考 Animation & Motion widgets, 和 Animations tutorial, 還有 Animations overview。
如何繪製到螢幕上
在iOS中,我們可以使用CoreGraphics
可以將線條或者圖形繪製到螢幕上。Flutter中擁有一套不同的Api,它基於Canvas
這個類,並結合 CustomPaint
和 CustomPainter
兩個類可以幫你實現螢幕繪製需求,CustomPainter
實現了繪製畫布的演算法。
在Flutter中,實現畫筆程式 可以參考Collin 在 StackOverflow 上的答案,原始碼在kitttn‘s github
\效果如下:
widget的透明度在哪
在iOS中每個元件都有.opacity 或者 .alpha 屬性表示透明度。在Flutter中,要是先透明需要使用透明的widget包裝一下widget才能實現。
如何實現自定義widget
In iOS, you typically subclass UIView
, or use a pre-existing view, to override and implement methods that achieve the desired behavior. In Flutter, build a custom widget by composing smaller widgets (instead of extending them).
在iOS中,我們可以繼承UIView
,或者使用已經存在的檢視,通過重新和實現方法來實現期望的行為。在Flutter中,構造一個自定的widget需要將系統提供的widget組合在一起。
For example, how do you build a CustomButton
that takes a label in the constructor? Create a CustomButton that composes a RaisedButton
with a label, rather than by extending RaisedButton
:
舉個例子:如何自定義一個CustomButton
, 它的構造方法中攜帶一個label?可以通過將RaisedButton
和label結合在一起,如下程式碼:
導航
如何在多個頁面之間跳轉
In iOS, to travel between view controllers, you can use a UINavigationController
that manages the stack of view controllers to display.
Flutter has a similar implementation, using a Navigator
and Routes
. A Route
is an abstraction for a “screen” or “page” of an app, and a Navigator
is a widget that manages routes. A route roughly maps to a UIViewController
. The navigator works in a similar way to the iOS UINavigationController
, in that it can push()
and pop()
routes depending on whether you want to navigate to, or back from, a view.
To navigate between pages, you have a couple options:
- Specify a
Map
of route names. - Directly navigate to a route.
The following example builds a Map.
在iOS中,多個viewController之間的轉化可以通過UINavigationController
來實現,它管理著一組viewController的顯示。
Flutter中有類似的實現,使用Navigator
和Router
來實現,一個Router
是一個app中screen
或者page
的抽象,Navigator
是一個widget,它管理著多個router。router近似於iOS中的UIViewController,navigator的工作機制和iOS中的UINavigationController
一致。所以它可以使用push()
和pop()
來將頁面導航到某個檢視或者返回到某個檢視。
在頁面之間跳轉,你可以使用下面的幾個方式:
- 指定一個由router名稱構成的map。
- 直接跳轉到一個路由
如何跳轉到另一個app
在 iOS 中,要跳轉到其他 App,你需要一個特定的 URL Scheme。對系統級別的 App 來說,這個 scheme 取決於 App。為了在 Flutter 中實現這個功能,你可以建立一個原生平臺的整合層,或者使用現有的 plugin,例如 url_launcher。
如何跳轉到iOS原生的viewController
呼叫SystemNavigator.pop()
方法相當於呼叫下面iOS程式碼:
UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([viewController isKindOfClass:[UINavigationController class]]) {
[((UINavigationController*)viewController) popViewControllerAnimated:NO];
}
複製程式碼
如果這樣達不到你的期望,你可以寫自己的跨平臺方案來呼叫iOS程式碼。platform channel
小結
本文主要從iOS開發者的角度講述了Flutter開發中的幾個點,不知道你是否有所收穫,本文還有第二篇文章,敬請期待。
本文主要參考Flutter官方文件,Flutter中文網。
由於排版原因,文中我使用了圖片的形式展示程式碼,如果你需要原始碼,可以關注我的公眾號,回覆關鍵字"flutter"獲取相關程式碼。
本文首發自微信公眾號【RiverLi】,歡迎你的關注與投稿。