Widget
在Flutter中,几乎所有的对象都是一个widget。与原生开发中的控件不同的是Flutter中的widget概念更广泛。它不仅可以表示UI元素,也可以表示一些功能性的组件如:用户手势检测的GestureDetector Widget、用于应用数据传递的Theme等等。由于Flutter主要就是用于构建用户界面,所以绝大多数的时候可以认为widget就是一个控件。
Widget的功能是描述一个UI元素的配置数据。Widget其实并不是表示最终绘制在设备屏幕上的显示元素,只是显示元素的一个配置数据。实际上在Flutter中真正代表屏幕上显示元素的类是Element。也就是说Widget是描述Element的一个配置。一个Widget可以对应多个Element,这是因为同一个Widget对象可以被添加到UI树的不同部分。到真正渲染的时候,UI树的每一个节点都会对应一个Element对象。
StatelessWidget 和 StatefulWidget 是Flutter中的基础组件。日常开发中自定义Widget都是选择继承这两者之一。也是在开发中接触最多的Widget。
- StatelessWidget:无状态的。展示信息,面向那些始终不变的UI控件。
- StatefulWidget:有状态的。可以通过改变状态使UI发生变化,可以包含用户交互。
在实际使用中Stateless和Stateful的选择取决于这个Widget是有状态还是无状态的,简单说就是看界面是否需要更新。
StatelessWidget
StatelessWidget用于不需要维护状态的场景,通常在build方法中通过嵌套其他Widget来构建UI。在构建的过程中会递归的构建其嵌套的Widget。
项目结构
如图所示,我们在lib下新建一个dart文件,命名为first_app。
// 导包
import 'package:flutter/cupertino.dart';
// main函数
void main()=>runApp(MyApp());
// 创建自己的widget继承StatelessWidget
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 创建一个TextView()
return Text("hello flutter");
}
}
注释很详细,就不再解释代码是什么意思。根据上述代码我们想得到的效果就是在屏幕上显示一个TextView,并显示“hello flutter”。那么我们能不能得到呢?运行一下就知道了:
第一次运行结果
很不幸的是竟然抛出异常了。。。上图中红色框空圈住的异常信息的意思分别是widget 库发生了异常 和 widget 没有给定方向。既然如此,那么我们就去看一下 Text 的源码。
这是Text类的一个常量构造方法。可以看到除了第一个参数 data(data是String类型)之外其他的参数都被 {} 括住了,这种用法叫可选命名参数。在这些可选参数中我们可以看到很多在Android中能见到的属性:maxLines,textAlign。其中有一个参数textDirection可能跟我们的异常有关。这个参数也确实是管理着文字的方向的。TextDirection是Text类中的一个枚举类: TextDirection
rtl代表right-to-left,也就是从右往左。ltr表示left-to-right,也就是从左向右。那么我们给我们的text添加一个方向再运行一下:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text("hello flutter",textDirection: TextDirection.ltr,);
}
}
TextDirection
终于看到结果了!!!虽然有点别扭,不过好歹看到效果了。可以看到text默认显示在了左上角。这也表示Flutter跟Android的坐标系是一样的,都是以左上角为原点。
下面来解决位置的问题。由于Flutter没有像Android中的LinearLayout或者RelativeLayout一样可以直接给设置一下属性就可以把控件摆放到我们想要的位置。不过我们可以通过Center这个专门处理位置的类来解决:
return Center(child: Text("hello flutter",textDirection: TextDirection.ltr,),);
Center
可以看到我们的hello flutter 成功的显示在了屏幕的正中间。解决了位置的问题,可是这界面也太难看了吧?首先应该知道Google在2014年推出了MaterialDesign的一种设计语言,说白了就是一种设计风格。我们现在的app大多也就是按到MaterialDesign这样来设计的。Flutter是Google推出的,自然也支持这种设计风格。那么我们看一下在Flutter中使用MaterialDesign的风格来美化一下我们的界面:
return MaterialApp(home: Center(child: Text("hello flutter",textDirection: TextDirection.ltr,),),);
MaterialApp
呃!!!好像更难看了😂不仅变成了红色还出现了两条下划线。这其实就是由MaterialApp提供的。在Android开发中我们经常要定义很多的style文件,在style文件中我们可以定义背景色,文本样式等等。那么在Flutter中也是同样的有很多这种style样式,可能默认就是我们所看到的那样。
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(
"hello flutter",
textDirection: TextDirection.ltr,
),
),
));
Scaffold
可以看到我们使用Scaffold再包装一下界面就变成了起码现在看起来算比较舒服的界面。Scaffold其实就是实现了MaterialDesign中的各种风格的配件:
Scaffold源码
可以看到有appBar,对应的就是我们Android中的ActionBar、还有drawer对应Android中的DrawerLayout等等。也就是说Scaffold包含了一个页面所需要很多元素。下面我们来尝试一下:
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("第一个页面"),
),
floatingActionButton: FloatingActionButton(child: Text("点击"),),
body: Center(
child: Text(
"hello flutter",
textDirection: TextDirection.ltr,
),
),
));
可以看到我们添加了一个appBar,和一个悬浮按钮,按钮的文字为点击:
StatefulWidget
上面的例子中我们的界面都是写死的界面,是不可交互的。下面我们就展示一下如何使用StatefulWidget来实现一下可交互的界面:
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("第一个页面"),
),
floatingActionButton: FloatingActionButton(child: Text("点击"),),
body: Center(
child: Text(
"hello flutter",
textDirection: TextDirection.ltr,
),
),
));
}
}
可以看到使用StatefulWidget之后又多出了一个类并继承了State。这个State就是用来管理Widget的状态的。可是仅仅这样做还是静态的页面,依然没办法交互。下面我们就修改一下代码,让中间的Text的文字3秒之后发生变化,并给悬浮按钮加上点击事件。当3秒之后我们再点击按钮来改变文字:
String data = "hello flutter"; // 初始文字
int count = 0; // 统计点击次数
class _MyAppState extends State<MyApp> {
_MyAppState(){
Future.delayed(Duration(seconds: 8)).then((t){
data = "我是TextView,我被改变了";
// 3秒之后触发刷新页面
setState(() {
});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("第一个页面"),
),
floatingActionButton: FloatingActionButton(child: Text("点击"),onPressed: (){ // onPress()就是点击事件
count++;
// 点击一次就刷新页面
setState(() {
});
},),
body: Center(
child: Text(
"${data}${count}", // 拼接上点击次数
textDirection: TextDirection.ltr,
),
),
));
}
}
动态交互效果
其中需要注意的一点是第一个 setState()方法是写在Future.then()的方法体之中的,也就是说Future把一个延时任务放入了Event_Queue当中来延迟执行。可如果我们直接在构造方法中调用setState()是会发生异常的:
红色框中的意思大概是setState()在构造方法中调用导致出现了异常。后面还告诉了我们生命周期状态是created,但是此刻没有widget,还没准备好。
state的生命周期
bool show = true;
class _MyAppState extends State<MyApp> {
_MyAppState(){
print("-----> parent constructor");
}
@override
Widget build(BuildContext context) {
print("-----> parent build");
return MaterialApp(
home: Scaffold(
body: Center(
child: RaisedButton(
onPressed: () {
setState(() {
show = !show;
});
},
child: show ? Child() : Text("1111111"),
),
),
),
);
}
@override
void reassemble() {
print("-----> parent reassemble");
}
@override
void didUpdateWidget(MyApp oldWidget) {
print("-----> parent didUpdateWidget");
}
@override
void initState() {
print("-----> parent initState");
}
@override
void didChangeDependencies() {
print("-----> parent didChangeDependencies");
}
@override
void deactivate() {
print("-----> parent deactivate");
}
@override
void dispose() {
print("-----> parent dispose");
}
}
class Child extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ChildState();
}
}
class ChildState extends State<Child> {
@override
Widget build(BuildContext context) {
print("-----> child build");
return Text(
"echo",
textDirection: TextDirection.ltr,
);
}
@override
void reassemble() {
print("-----> child reassemble");
}
@override
void didUpdateWidget(Child oldWidget) {
print("-----> child didUpdateWidget");
}
@override
void initState() {
print("-----> child initState");
}
@override
void didChangeDependencies() {
print("-----> child didChangeDependencies");
}
@override
void deactivate() {
print("-----> child deactivate");
}
@override
void dispose() {
super.dispose();
print("-----> child dispose");
}
}
上面的代码其实很简单,在屏幕的中央有一个按钮,点击按钮的时候文字内容会在 echo 和 111111 之间切换,同时打印state的生命周期方法。
上图中红色框内是运行完app之后state的生命周期流程。可以看到无论是parent还是child都依次执行了initState->didChangeDependencies->build。下面的黄色框是点击按钮之后的生命周期流程。parent又调用了 build 方法是因为在按钮的点击事件调用了setState()改变了UI界面,相当于parent重绘了一次。而child由于在点击事件内被换成了一个新的Text,相当于被销毁了,于是就执行了deactivate->dispose。
生命周期对比
上图是和Android中Activity的生命周期的对比,我们可以这样去记忆。
网友评论