盒子
盒子
Posts List
  1. 特性
  2. 呈现原理
  3. 下期预告

从零开始的Flutter之旅: StatelessWidget

这次要展示的是什么是Flutter的Widget,即小部件;以及如何在Flutter中使用StatelessWidget,即无状态小部件。

至于Flutter,通俗的讲是开发者可以通一套简单的代码来同时构建Android与IOS应用程序。

特性

小部件是Flutter应用程序的基本构建模块,每一个都是不可变的声明,也是用户界面的一部分。例如button,text,color以及布局所用到的padding等等。

下面我们来看flutter_github中的一个实例。

圈选中的item只有两个信息,头像与名称。为了避免代码的重复使用,将其抽离成一个独立的widget,具体代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class FollowersItemView extends StatelessWidget {
final GestureTapCallback tapCallback;
final String avatarUrl;
final String name;
const FollowersItemView(
{Key key, this.avatarUrl, this.name, this.tapCallback})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 15.0),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: tapCallback,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
FadeInImage.assetNetwork(
placeholder: 'images/app_welcome.png',
image: avatarUrl,
width: 80.0,
height: 80.0,
),
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 15.0),
child: Text(
name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(
color: Colors.grey[600],
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
),
)
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 15.0),
child: Divider(
thickness: 1.0,
color: colorEAEAEA,
height: 1.0,
endIndent: 0.0,
),
),
],
),
),
);
}
}

它继承于StatelessWidget,StatelessWidget的特性是无状态,数据不可变化。这个性质正好符合我们将要抽离的部件。抽离的部件需要做头像与名称的展示,没有任何形式上的交互变化。唯一的一个交互也是点击,但它并没有涉及数据的改变。所以在代码中将这些数据定义成final类型。本质就如Text部件,并没有如输入文本或者带有动画的部件一样随着时间内部属性会有所变化。

既然没有任何变化,那么我们也可以将其构造函数定义为const类型。

有了上面的部件抽离,我们就可以直接在ListView中使用该无状态部件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@override
Widget createContentWidget() {
return RefreshIndicator(
onRefresh: vm.handlerRefresh,
child: Scrollbar(
child: ListView.builder(
padding: EdgeInsets.only(top: 15.0),
itemCount: vm.list?.length ?? 0,
itemBuilder: (BuildContext context, int index) {
final item = vm.list[index];
return FollowersItemView(
avatarUrl: item.avatar_url,
name: item.login,
tapCallback: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return WebViewPage(title: item.login, url: item.html_url);
}));
},
);
}),
),
);
}

在ListView中引用FollowItemView,并传入不变的数据即可。

呈现原理

现在StatelessWidget的使用大家都会了,那它是如何调用的呢?

下面我们来看下它的呈现原理。

正如开头所说的将小部件作为Flutter应用构建的基础,在Flutter中我们将小部件的构建称作为Widget Tree,即小部件树。它就像是应用程序的蓝图,我们将蓝图创建好,然后内部会通过蓝图去创建对应显示在屏幕上的element元素。它包含了蓝图上对应的小部件的配置信息。所以对应的还有一个Element Tree,即元素树。

每一个StatelWidget都有一个StatelessElement,内部会通过createElement()方法进行创建其实例

1
2
@override
StatelessElement createElement() => StatelessElement(this);

同时在StatelessElement中会通过buid()方法来获取StalessWidget中所构建的蓝图Widget,并将元素显示到屏幕上。

Widget Tree与Element Tree之间的交互如下

FollowerItemView中的StatelessElement会调用build方法来获取它是否有子部件,如果有的话对应的子部件也会创建它们自己的Element,并把它安装到元素树上。

所以我们的程序有两颗对应的树,其中一颗代表屏幕上显示的内容Element;另一颗树代表其展示的蓝图Widget,它们由许多的小部件组成。

而我们开发人员所做的就是将这些不同的小部件构建成我们所需要的应用程序。

最后,我们再来了解下最初的安装入口。

1
2
3
void main() {
runApp(GithubApp());
}

在我们的main文件中,有一个main函数,其中调用了runApp方法,传入的是GithubApp。我们再来看下GithubApp是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class GithubApp extends StatefulWidget {
@override
_GithubAppState createState() {
return _GithubAppState();
}
}
class _GithubAppState extends State<GithubApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Github',
theme: ThemeData.light(),
initialRoute: welcomeRoute.routeName,
routes: {
welcomeRoute.routeName: (BuildContext context) => WelcomePage(),
loginRoute.routeName: (BuildContext context) => LoginPage(),
homeRoute.routeName: (BuildContext context) => HomePage(),
repositoryRoute.routeName: (BuildContext context) => RepositoryPage(),
followersRoute.routeName: (BuildContext context) =>
FollowersPage(followersRoute.pageType),
followingRoute.routeName: (BuildContext context) =>
FollowersPage(followingRoute.pageType),
webViewRoute.routeName: (BuildContext context) => WebViewPage(title: '',),
},
);
}
}

发现没它其实也是一个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是一个不错的选择。

下期预告

从零开始的Flutter之旅: StatefulWidget

如果你喜欢我的文章模式,或者对我接下来的文章感兴趣,建议您关注我的微信公众号:【Android补给站】

或者扫描下方二维码,与我建立有效的沟通,同时更快更准的收到我的更新推送。

支持一下
赞赏是一门艺术