从零开始的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是一个不错的选择。