從零開始的Flutter之旅: StatelessWidget
- 2020 年 3 月 10 日
- 筆記
Rouse
讀完需要
12
分鐘
速讀僅需 4 分鐘
這次要展示的是什麼是 Flutter 的 Widget,即小部件;以及如何在 Flutter 中使用 StatelessWidget,即無狀態小部件。
至於 Flutter,通俗的講是開發者可以通一套簡單的程式碼來同時構建 Android 與 IOS 應用程式。
1
特性
小部件是 Flutter 應用程式的基本構建模組,每一個都是不可變的聲明,也是用戶介面的一部分。例如 button,text,color 以及布局所用到的 padding 等等。
下面我們來看flutter_github中的一個實例。(項目鏈接在文章底部)

圈選中的 item 只有兩個資訊,頭像與名稱。為了避免程式碼的重複使用,將其抽離成一個獨立的 widget,具體程式碼如下
1class FollowersItemView extends StatelessWidget { 2 final GestureTapCallback tapCallback; 3 final String avatarUrl; 4 final String name; 5 6 const FollowersItemView( 7 {Key key, this.avatarUrl, this.name, this.tapCallback}) 8 : super(key: key); 9 10 @override 11 Widget build(BuildContext context) { 12 return Container( 13 padding: EdgeInsets.symmetric(horizontal: 15.0), 14 child: GestureDetector( 15 behavior: HitTestBehavior.opaque, 16 onTap: tapCallback, 17 child: Column( 18 children: <Widget>[ 19 Row( 20 children: <Widget>[ 21 FadeInImage.assetNetwork( 22 placeholder: 'images/app_welcome.png', 23 image: avatarUrl, 24 width: 80.0, 25 height: 80.0, 26 ), 27 Expanded( 28 child: Padding( 29 padding: EdgeInsets.only(left: 15.0), 30 child: Text( 31 name, 32 overflow: TextOverflow.ellipsis, 33 maxLines: 1, 34 style: TextStyle( 35 color: Colors.grey[600], 36 fontSize: 20.0, 37 fontWeight: FontWeight.bold, 38 ), 39 ), 40 ), 41 ) 42 ], 43 ), 44 Padding( 45 padding: EdgeInsets.symmetric(vertical: 15.0), 46 child: Divider( 47 thickness: 1.0, 48 color: colorEAEAEA, 49 height: 1.0, 50 endIndent: 0.0, 51 ), 52 ), 53 ], 54 ), 55 ), 56 ); 57 } 58}
它繼承於 StatelessWidget,StatelessWidget 的特性是無狀態,數據不可變化。這個性質正好符合我們將要抽離的部件。抽離的部件需要做頭像與名稱的展示,沒有任何形式上的交互變化。唯一的一個交互也是點擊,但它並沒有涉及數據的改變。所以在程式碼中將這些數據定義成 final 類型。本質就如 Text 部件,並沒有如輸入文本或者帶有動畫的部件一樣隨著時間內部屬性會有所變化。
既然沒有任何變化,那麼我們也可以將其構造函數定義為 const 類型。
有了上面的部件抽離,我們就可以直接在 ListView 中使用該無狀態部件
1 @override 2 Widget createContentWidget() { 3 return RefreshIndicator( 4 onRefresh: vm.handlerRefresh, 5 child: Scrollbar( 6 child: ListView.builder( 7 padding: EdgeInsets.only(top: 15.0), 8 itemCount: vm.list?.length ?? 0, 9 itemBuilder: (BuildContext context, int index) { 10 final item = vm.list[index]; 11 return FollowersItemView( 12 avatarUrl: item.avatar_url, 13 name: item.login, 14 tapCallback: () { 15 Navigator.push(context, MaterialPageRoute(builder: (_) { 16 return WebViewPage(title: item.login, url: item.html_url); 17 })); 18 }, 19 ); 20 }), 21 ), 22 ); 23 }
在 ListView 中引用 FollowItemView,並傳入不變的數據即可。
2
呈現原理
現在 StatelessWidget 的使用大家都會了,那它是如何調用的呢?
下面我們來看下它的呈現原理。
正如開頭所說的將小部件作為 Flutter 應用構建的基礎,在 Flutter 中我們將小部件的構建稱作為 Widget Tree,即小部件樹。它就像是應用程式的藍圖,我們將藍圖創建好,然後內部會通過藍圖去創建對應顯示在螢幕上的 element 元素。它包含了藍圖上對應的小部件的配置資訊。所以對應的還有一個 Element Tree,即元素樹。
每一個 StatelWidget 都有一個 StatelessElement,內部會通過 createElement()方法進行創建其實例
1 @override 2 StatelessElement createElement() => StatelessElement(this);
同時在 StatelessElement 中會通過 buid()方法來獲取 StalessWidget 中所構建的藍圖 Widget,並將元素顯示到螢幕上。
Widget Tree 與 Element Tree 之間的交互如下

FollowerItemView 中的 StatelessElement 會調用 build 方法來獲取它是否有子部件,如果有的話對應的子部件也會創建它們自己的 Element,並把它安裝到元素樹上。
所以我們的程式有兩顆對應的樹,其中一顆代表螢幕上顯示的內容 Element;另一顆樹代表其展示的藍圖 Widget,它們由許多的小部件組成。
而我們開發人員所做的就是將這些不同的小部件構建成我們所需要的應用程式。
最後,我們再來了解下最初的安裝入口。
1void main() { 2 runApp(GithubApp()); 3}
在我們的 main 文件中,有一個 main 函數,其中調用了 runApp 方法,傳入的是 GithubApp。我們再來看下 GithubApp 是什麼?
1class GithubApp extends StatefulWidget { 2 @override 3 _GithubAppState createState() { 4 return _GithubAppState(); 5 } 6} 7 8class _GithubAppState extends State<GithubApp> { 9 @override 10 Widget build(BuildContext context) { 11 return MaterialApp( 12 title: 'Flutter Github', 13 theme: ThemeData.light(), 14 initialRoute: welcomeRoute.routeName, 15 routes: { 16 welcomeRoute.routeName: (BuildContext context) => WelcomePage(), 17 loginRoute.routeName: (BuildContext context) => LoginPage(), 18 homeRoute.routeName: (BuildContext context) => HomePage(), 19 repositoryRoute.routeName: (BuildContext context) => RepositoryPage(), 20 followersRoute.routeName: (BuildContext context) => 21 FollowersPage(followersRoute.pageType), 22 followingRoute.routeName: (BuildContext context) => 23 FollowersPage(followingRoute.pageType), 24 webViewRoute.routeName: (BuildContext context) => WebViewPage(title: '',), 25 }, 26 ); 27 } 28}
發現沒它其實也是一個 Widget,正如文章開頭所說的,Flutter 是由各個 Widget 組成。main 是程式的入口,而其中的 runApp 中的 Widget 是整個程式掛載的起點。它會創建成一個具有與螢幕寬高一致的根元素,並把它裝載到螢幕中。
所以在 Flutter 中一直都是通過創建 Element,然後調用 build 方法來獲取其後續的子 Widget,最終構建成我們所看到的程式。
文中的程式碼都是來自於flutter_github,這是一個基於 Flutter 的 Github 客戶端同時支援 Android 與 IOS,支援賬戶密碼與認證登陸。使用 dart 語言進行開發,項目架構是基於 Model/State/ViewModel 的 MSVM;使用 Navigator 進行頁面的跳轉;網路框架使用了 dio。項目正在持續更新中,感興趣的可以關注一下。
當然如果你想了解 Android 原生,相信flutter_github的純 Android 版本AwesomeGithub是一個不錯的選擇。