Flutter开发环境搭建好后,就可以开始开发了。
有状态组件与无状态组件
在Flutter 中,一切皆组件:Widget。组件Widget又分为两种:
1.StatefulWidget(有状态组件):如果一个控件,它需要随着用户交互或者内部的值、状态需要根据不同的外部业务变化而改变的话,那么这个组件就需要被设计为有状态的。比如TextField输入随着输入变化而变化。
2.StatelessWidget(无状态组件):对比有状态的组件,无状态的组件是不变的,不需要随着业务变化而变化,比如一个固定的Text文本展示控件,他是不需要随着用户交互而改变的,我们把它定为无状态的。
在Flutter中,没有Controller和Activity的控制器的概念,所有的page也都是一个Widget. 一个页面基本上都是有交互的,所以,一般我们新建一个page都是继承:StatefulWidget,示例代码:
class CommonWebPage extends StatefulWidget {
final String url;
//添加构造函数
CommonWebPage({this.url});
@override
State createState() {
return _CommonWebPageState();
}
}
上面例子中,我们新建了一个名为CommonWebPage的页面,由于它是由状态的,所以必须要复写他的状态方法:createState(),这个状态方法返回的是专门管理这个page的状态类:_CommonWebPageState。
那么这个状态类又是什么呢?下面代码就是这个状态类:
class _CommonWebPageState extends State {
@override
Widget build(BuildContext context) {
return new WebviewScaffold(
url: widget.url,
appBar: _navigationBar(),
);
}
}
上面代码中,这个状态类继承State,由于这个状态类是为CommonWebPage页面服务的,所以泛型为这个页面。这些是dart语法,前期可以先不用关注dart的语法,先记住这个固定的写法:
先创建一个继承StatefulWidget的页面类,然后在创建一个继承State
这个build()其实就是这个页面里面的布局组件了。bulid里面布局的控件即渲染出来的页面样式。
页面状态变化更新机制
前面讲了有状态组件,你肯定会想知道,既然是有状态交互变化,那么状态值变化怎么让页面更新的呢?这里就要说一下setState()。
setState()
Flutter通过setState来重新渲染页面,也就是说:如果代码执行了setState()方法,那么build()会重新执行一遍,build()里面会根据最新的页面显示重新渲染新的页面。【其实这套机制和RN是一样,如果你以前了解过ReactNative的话】。
现在我们举个例子说明:
下图是要给webpage页面加载h5的过程,当页面在加载过程中,中间标题为空,当加载结束后,导航栏上显示标题。
我们首先需要定义一个变量title:
String title; //默认null
我们页面build()代码如下:
@override
Widget build(BuildContext context) {
return new WebviewScaffold(
url: widget.url,
appBar: _navigationBar(),
);
}
PreferredSizeWidget _navigationBar () {
return PreferredSize(
child: new Stack(
children: [
new CupertinoNavigationBar(
middle: Text(this.title==null?"":this.title),
border: Border.all(width: 0, color: CupertinoColors.darkBackgroundGray),
),
new Positioned(
child: _progressBar(),
left: 0,
bottom: 0,
width: window.physicalSize.width/2,
height: loading ? 2 : 0,
)
],
),
preferredSize: Size(window.physicalSize.width/2,44),
);
}
我们关注中间设置标题的代码:
middle: Text(this.title==null?"":this.title),
这句话很好解释,当title为空的时候,标题显示空字符(即不显示),有内容就显示标题内容。当页面加载中的时候,title为空,所以显示成上图一的效果。那么如果在加载完成后,title获取了值后,让页面重新显示成图二呢?
那么我们再页面加载完成,获取title的代码:
//获取h5页面标题
Future getWebTitle() async {
String script = 'window.document.title';
var title = await
flutterWebViewPlugin.evalJavascript(script);
setState(() {
this.title = title;
print('#################### $title');
});
return title;
}
其中关键代码:
setState(() {
this.title = title;
});
这只title的代码放在了setState()里面,那么该代码执行完成后,会自动触发build()方法重新执行,重新执行到middle: Text(this.title==null?"":this.title),这时候title就已经有值了,页面标题就显示出来了。
页面布局
Flutter页面布局采用的是Flex布局原理,和ReactNative是一样的。如果你之前熟悉Flex布局,那么掌握Flutter页面布局将非常容易上手。
以下面页面为例:
上图展现的是一个商品列表的布局样式:整体:左右模式(Row); 右边里面:上下模式(Column)。
下面贴出代码:
new Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
new Container(
margin: new EdgeInsets.fromLTRB(0, 0, 12, 10),
color: new Color(0xfff5f5f5),
width: 100,
height: 100,
child: new Image(image: NetworkImage(item.imageUrl),fit: BoxFit.cover,)
),
Expanded(
child: new Container(
height: 100,
child: rightColomnWidget(index)
),
)
],
),
//右边的列布局
Widget rightColomnWidget(int index) {
GoodsModel item = dataList[index];
return new Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
new Text(
item.goodsName,
style: new TextStyle(
color: CupertinoColors.darkBackgroundGray,
fontSize: 18,
fontWeight: FontWeight.w600,
)
),
new Text(
item.goodsDesc,
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: new TextStyle(
color: CupertinoColors.darkBackgroundGray,
fontSize: 15,
)
),
new Text(
item.goodsPrice + '元',
style: new TextStyle(
color: CupertinoColors.destructiveRed,
fontSize: 18,
fontWeight: FontWeight.w500,
)
)
],
);
}
mainAxisAlignment: 主轴方向子元素如何排列。
crossAxisAlignment:次轴方向子元素如何排列。
Container布局里可以设置margin和padding。
只有Row和Column的子元素可以是多个(children);
其他的布局组件(Container, Center,Padding,Expanded)子元素只能一个:child.
Expanded组件表示:父容器剩余的空间应该如何利用:
flex: 0 自己尽量不扩展自己的大小。
flex: 1 占满父容器剩余的空间。
一般我们都是设置flex:1 (默认是1,可以不写)。
列表组件:ListView
ListView是Flutter内置的组件,相当于iOS 中的UITableview.
ListView用法:
new ListView.builder(
itemCount: dataList.length,
itemBuilder: (context, index){
return new Center(
child: new Container(
margin: new EdgeInsets.fromLTRB(12, 5, 12, 5),
child: new Column(
children: [
rowWidget(index),
//分隔线
new Divider(height: 0.5,)
],
)
),
);
},
itemCount表示列表元素个数;
itemBuilder:迭代的每一行里面的cell布局。
这期暂时讲到这里,下期讲网络请求。