万物都是Widget
如同Android中界面都是由View构成一样,Flutter的界面均由Widget构成。
Android使用Activity或者Fragment等进行View的管理,而Flutter更为直接,全部由Widget内部进行管理。所以Flutter并不使用xml进行样式的描述,而是直接使用Dart代码进行一层层的布局嵌套书写,并在其中夹杂状态的管理,数据加载等操作,这一点需要Android开发人员适应。
牢记万物都是Widget
无论是单个的View,或者是掌管排列的Layout,甚至是手势装饰类,都是Widget的一种。
Widget创建之后,是不可变(immutable)的,所以当你试图动态更改一个Textview的style的时候,你会发现这些变量都是final的,改变一个Widget的方法只有重建它,而使用什么方法进行重建呢?这就要谈到State
State
我们将Widget大致分为两种
- StatelessWidget(无状态的)
如果你所绘制的界面,无须依赖外界数据(就是不会发动态更改),你可以选择继承此Widget,这会让你直接返回一个Widet树,而没有方法进行重绘.
- StatefulWidget(有状态的)
如果你需要随时刷新界面,则需要继承此类,而后你需要强制重写CreateState函数,返回一个State对象
class LoginPage extends StatefulWidget{
@override
State<StatefulWidget> createState() {
return LoginPageState();
}
}
这里的State就是我们进行状态管理的一个工具,具体的Widget树将有State进行返回。
如下图
class LoginPageState extends State{
@override
Widget build(BuildContext context) {
return Widget();
}
继承一个State会让你重写Build方法,以此来返回Widget树。
而这个State对象就掌管了这个Widget
现在我们来看看拥有了State之后,如何通知界面改变。
我们现在有一个需要,显示软件的版本名称,
所以我们需要一个Text()来进行显示,还需要一个变量 String versionName,并将这个变量传入Text,最后我们书写获取版本号的异步方法。
String nameVersion ="";
class LoginPageState extends State{
@override
Widget build(BuildContext context) {
return Text(nameViersion);
}
void getVersionAndBuilderNumber () async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String number = packageInfo.buildNumber;
String version = packageInfo.version;
print(version);
setState(() {
nameVersion = "$number($version)";
});
}
注意,我们必须设置一个变量,因为我们提到Widget是不可变的,你不能在获取到版本号之后(这个过程是异步)再获取Text进行赋值,我们需要抛弃Android或者iOS开发中的惯性,Flutter是数据驱动的,你只需要更改你的数据,并通知刷新,那些绑定了对应数据的Widget会自动重绘,可能看到这里,使用过Android中数据绑定的同学会会心一笑(虽然机制完全不同)。
最后我们在入口处,比如State的构造方法中,调用getVersion()
LoginPageState(){
getVersionAndBuilderNumber();
}
这样只要创建的了State对象,getVersion方法将异步获取版本号(网络请求同理),注意看getVersion()中获取到版本号之后调用的
setState()这个函数的意思就是通知Widget进行刷新,不要担心重复绘制,Flutter会自己决定哪些部分需要刷新。
在setState()中对nameVersion的重新赋值取决你自己的习惯,你也可以在setState()之前进行重新赋值,只要调用了setState(),任何之前的数据改变都将会进行更改。
Widget树
我们已经学会如何单独绘制一个Widget,并且进行动态的数据更新,而这一段我们来继续了解State类中,是如何进行复杂布局的。
继承State之后,我们需要实现Build()方法来返回一个Widget,如同上述代码,我们单单只返回了一个Text,它是一个Wdiget,如同TextView一样,是单个View。
类似Android,Flutter的Widget也提供了一些用来布局的Widget,比如Cloum(),Row(),记住万物都是Widget,只要接收参数是Widget的地方,都可以New这些类来进行布局,包括Cloum(),Row()内部需要的Childs[]也就是Widet数组。
child:Column(children: <Widget>[
SizedBox(
height:44,
child:TextField(
keyboardType: TextInputType.number,
cursorColor: Color(0x66FFFFFF),
style: TextStyle(
color: Colors.white,
fontSize:15
),
decoration: InputDecoration(
hintText: 'Phone number',
hintStyle: TextStyle(
color: Color(0x66FFFFFF),
fontSize: 15
),
border: InputBorder.none
),
),
)
这段代码里 最外层是一个Cloum() 表示竖向线性布局,它需要一个Widget的数组(child[])来填充,在child[]数组li,我们New了一个SizeBox()
SizeBox是一个用来描述大小的BoxWidget,Text这类Widget的大小常常需要已Parent来描述尺寸,这是反Android开发习惯的一个地方
SizeBox()中,我们又制造了TextFiled(),这是可编辑的Text(就是EditText)
于是,我们构造了一个可输入的Text,并指明了宽高,如果还没有熟悉的人,可能对着一层层的嵌套感到可怕,然而,通过Idea等编辑器的优化,实际读着没那么费劲,而且这种数据绑定的方式,节省了很多get set程序。
同理 我们一开始生命的LoginPage也是一种Widget,所以,我们也可以在Build()中返回其他的StatefulWidget()
这样,当组合复杂的Widget的时候,将一些带有业务逻辑的Widget进行分离,避免搅作一团.
class LoginPageState extends State{
@override
Widget build(BuildContext context) {
return XXXXPage();********
}
网友评论