前言
接Flutter基础控件篇[2],继续往下罗列。
这一篇主要记录Table、ListView、GridView、SingleChildScrollView、SimpleDialog、AlertDialog、自定义弹窗还有一个BottomSheet。
正文
Table
类似于Android的TableLayout,桌面布局,就跟手机桌面似的,几行几列。
基本使用:
Widget build(BuildContext context) {
return Table(
children: [
TableRow(children: [
Container(color: Colors.blue[200], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 100,),
Container(color: Colors.blue[200], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 70,),
Container(color: Colors.blue[200], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 70,),
]),
TableRow(children: [
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 70,),
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 100, alignment: Alignment.center, child: Text('Table, 3行3列'),),
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 70,),
]),
TableRow(children: [
Container(color: Colors.blue[400], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 70,),
Container(color: Colors.blue[400], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 70,),
Container(color: Colors.blue[400], margin: EdgeInsets.fromLTRB(5, 0, 5, 0), width: 70, height: 100,),
])
],
//横向宽度,权重,这里这样写分别占总宽度的1/4, 2/4, 1/4
columnWidths: {0: FlexColumnWidth(1),1: FlexColumnWidth(2),2: FlexColumnWidth(1)},
// columnWidths: {0: FixedColumnWidth(100),1: FixedColumnWidth(70),2: FixedColumnWidth(130)},//具体宽度,这里这样写宽度分别为100,70,130
defaultVerticalAlignment: TableCellVerticalAlignment.middle, //纵向的对齐方式
);
}
主要属性:
children:子控件,参数是一个List<TableRow>,TableRow的子控件是一个List<Widget>,这样就构成了一个二维数组来排列子控件。List<Widget>具体布局每一行,List<TableRow>再把这每一行罗列起来,构成一个矩阵布局。
columnWidths:每一列的宽度,参数是一个Map<int, TableColumnWidth>,它规定了每一列展示的宽度。这里提供两种常用的写法:
- columnWidths: {0: FixedColumnWidth(100),1: FixedColumnWidth(70),2: FixedColumnWidth(130)} ,宽度固定,这样写第1、2、3列的宽度分别是100,70,130;
- columnWidths: {0: FlexColumnWidth(1),1: FlexColumnWidth(2),2: FlexColumnWidth(1)},按比例分配宽度,这里很像Android中的权重(Weight)。这样写总共将宽度分为4份,第一列占1/4,第二列占2/4,第三列占1/4;
border:背景边框,设置边框线,圆角之类。
defaultVerticalAlignment:纵向的对齐方式,参数是TableCellVerticalAlignment的几个固定值,top、middle、bottom、baseline、fill(填满高度);
defaultColumnWidth:默认每列宽度,统一设定每一行的宽度,按官方文档建议的值写 FlexColumnWidth(1.0),那就是均分,如果你写成一个固定值 FixedColumnWidth(70),那就是每列宽度都是70。
GridView
网格布局,名字个Android的GridVie一样,功能也基本一样。
基本使用:
Widget build(BuildContext context) {
return GridView(
scrollDirection: Axis.vertical,
reverse: false,
controller: scrollController,
// primary: true,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //横轴三个子widget
childAspectRatio: 1.3 //宽高比为1
),
children: <Widget>[
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 5, 5, 5), width: 70, height: 70,),
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 5, 5, 5), width: 70, height: 70,),
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 5, 5, 5), width: 70, height: 70,),
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 5, 5, 5), width: 70, height: 70,),
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 5, 5, 5), width: 70, height: 70,),
Container(color: Colors.blue[300], margin: EdgeInsets.fromLTRB(5, 5, 5, 5), width: 70, height: 70,),
],
);
}
主要属性:
scrollDirection: 滚动方向,Axis.vertical(竖直方向);Axis.horizontal(水平方向);
reverse:子控件排序是否反转;
controller: 滚动控制器,监听和控制滚动用到,比如写下拉加载更多之类的功能就可以用到这个。可以参考ScrollController 滚动监听及控制。
shrinkWrap:该属性表示是否根据子组件的总长度来设置GridView的长度,默认值为false 。默认情况下,GridView的会在滚动方向尽可能多的占用空间。当GridView在一个无边界(滚动方向上)的容器中时,shrinkWrap必须为true。
gridDelegate: 控制子控件排列规则的参数,包括配置子控件横纵方向间距,纵轴方向子控件个数,子控件宽高比例等参数,详细文档参考GridView。
然后还有几个不太被关注的属性:
primary: bool型,为true时,表示当子控件没有超出GridView的尺寸时,它依然是可滚动的,当scrollDirection是Axis.vertical并且controller不为null时它的默认值是true(试了一下当controller不为null,手动将primary设置为true的话会报错)。这个感觉不好理解,我试了一下,如果在子控件没有超出父控件尺寸时,把这个设置为true,效果如下:
拖动的时候会有这个拉到尽头的反馈效果,但好像对实际使用没啥影响。苹果手机没有试过,Android设备上这个属性应该不太重要。
cacheExtent:设置预加载的区域, cacheExtent 设置为 0.0,则关闭了“预加载”。
addAutomaticKeepAlives: 该属性表示是否将列表项(子组件)包裹在AutomaticKeepAlive 组件中;典型地,在一个懒加载列表中,如果将列表项包裹在AutomaticKeepAlive中,在该列表项滑出视口时它也不会被GC(垃圾回收),它会使用KeepAliveNotification来保存其状态。如果列表项自己维护其KeepAlive状态,那么此参数必须置为false。(参考Flutter中文网的资料)
addRepaintBoundaries: 该属性表示是否将列表项(子组件)包裹在RepaintBoundary组件中。当可滚动组件滚动时,将列表项包裹在RepaintBoundary中可以避免列表项重绘,但是当列表项重绘的开销非常小(如一个颜色块,或者一个较短的文本)时,不添加RepaintBoundary反而会更高效。和addAutomaticKeepAlive一样,如果列表项自己维护其KeepAlive状态,那么此参数必须置为false。(参考Flutter中文网的资料)
IndexedSemantics: 关于Semantics(语义),这里有一篇文章讲得稍微详细一点,Flutter中的Semantics。
ListView
列表控件,这个在实际开发过程中使用的最多了。
这里写法基本分为两类,第一类写的很死,适合列表项比较固定的情况:
ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(20.0),
children: <Widget>[
const Text('I\'m dedicating every day to you'),
const Text('Domestic life was never quite my style'),
const Text('When you smile, you knock me out, I fall apart'),
const Text('And I thought I was so smart'),
],
);
这样写基本上列表的数目和内容都是固定的,实际情况中应该很少会用这种写法。大多数实际需求中,列表展示的具体内容是未知的,列表的条数也是未知的,甚至有时候稍微复杂的列表,根据不同的数据类型,它的item布局都不相同,只有在获取到具体的数据之后,我们才知道这些信息,所以为了灵活的处理和展示列表数据,下面两种写法比较多一点:
1. ListView.builder
ListView.builder(
itemCount: data.length,
itemExtent: 50.0, //强制高度为50.0
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text("$index"));
}
);
itemBuilder作为列表项的布局构建器,具体构建每个位置上的item布局,并展示相应的内容。itemCount配置列表的item条数,可以根据具体的数据来灵活配置。
2. ListView.separated
相对于ListView.builder,ListView.separated增加了一个分割线的构造器separatorBuilder,分割线在实际开发中也是很常用到的。
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Container(
height: 60,
child: FlatButton( onPressed: () {}, child: Text(menus[index],)));
},
separatorBuilder: (BuildContext context, int index) {
return Container(height: 1,color: Color(0xffe2e8ed),);
},
itemCount: menus.length);
}
ListView主要属性:
itemExtent: 此参数如果不为null,则会强制children的“长度”为itemExtent的值;这里的“长度”是指滚动方向上子组件的长度,也就是说如果滚动方向是垂直方向,则itemExtent代表子组件的高度;如果滚动方向为水平方向,则itemExtent就代表子组件的宽度。在ListView中,指定itemExtent比让子组件自己决定自身长度会更高效,这是因为指定itemExtent后,滚动系统可以提前知道列表的长度,而无需每次构建子组件时都去再计算一下,尤其是在滚动位置频繁变化时(滚动系统需要频繁去计算列表高度);
itemCount: item条数;
itemBuilder、separatorBuilder的作用在上面代码中都能看得比较清晰;
其他的包括scrollDirection、reverse、controller、shrinkWrap、addRepaintBoundaries、addAutomaticKeepAlives等等这些基本上和GridView是一样的,直接参考上面GridView就可以了。
SingleChildScrollView
单一子控件的滚动布局,跟Android的ScrollView基本一样,Android的ScrollView也是只能有一个子控件,不然就会报错。
基本使用很简单:
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.vertical,
reverse:false,
controller:controller,
child: Column(
children: <Widget>[
//...
],
),
);
}
弹窗
在Flutter中,对话框会有两种风格,调用showDialog()方法展示的是material风格的对话框,调用showCupertinoDialog()方法展示的是ios风格的对话框。
而这两个方法其实都会去调用showGeneralDialog()方法,可以从源码中看到最后是利用Navigator.of(context, rootNavigator: true).push()一个页面。
所以,反正都是跳一个页面,那么当需要自定义自己的弹窗的时候,直接自己写一个弹窗样式的页面,调用showDialog()跳过去就完了。
下面是两个基本的弹窗模板
SimpleDialog
void _showSimpleDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: Text('SimpleDialog'),
titlePadding: EdgeInsets.fromLTRB(20, 20, 20, 10),
contentPadding: EdgeInsets.fromLTRB(20, 10, 20, 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8))), //圆角矩形
children: <Widget>[
SimpleDialogOption(
child: Text('button1'),
onPressed: () {
WidgetUtils.showToast('button1');
},
),
SimpleDialogOption(
child: Text('button2'),
onPressed: () {
WidgetUtils.showToast('button2');
},
)
],
);
});
}
效果是这样:
AlertDialog
void _showAlertDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('AlertDialog'),
content: Text('这是一个AlertDialog'),
titlePadding: EdgeInsets.fromLTRB(20, 20, 20, 10),
contentPadding: EdgeInsets.fromLTRB(20, 10, 20, 20),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(8))),
//圆角矩形
actions: <Widget>[
RaisedButton(
child: Text('button1'),
color: Colors.white,
disabledColor: Colors.white,
),
RaisedButton(
child: Text('button2'),
color: Colors.white,
disabledColor: Colors.white,
),
],
);
});
}
效果如下:
BottomSheet
从底部弹窗,可以直接调用showModalBottomSheet方法:
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
height: 150,
color: Colors.white,
child: Column(
children: <Widget>[
Container(
child:FlatButton(child:Text('item1', style: TextStyle(fontSize: 17)),onPressed: (){},color: Colors.white,),
alignment: Alignment.center,
height: 50,
),
Container(
child:FlatButton(child:Text('item2', style: TextStyle(fontSize: 17)),onPressed: (){},color: Colors.white,),
alignment: Alignment.center,
height: 50,
),
Container(
child:FlatButton(child:Text('item3', style: TextStyle(fontSize: 17)),onPressed: (){},color: Colors.white,),
alignment: Alignment.center,
height: 50,
),
],
));
}
)
上面的代码效果如下:
按模板弹窗实际使用应该比较少,用到的属性也比较简单,看文档基本都能明白。
然后看看自定义弹窗,其实也很简单。直接调用showDialog()方法,里面的布局自己随意写,背景,样式,窗体内容,窗体位置等等等等,自由发挥。下面是一个简单的样板。
void _showMyDialog(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.pop(context);//点击外部阴影 弹窗消失
},
child: Container(
color: Colors.transparent,
alignment: Alignment.center,
child: GestureDetector(
onTap: () {},//点击弹窗主体,自定义事件,覆盖父类的点击事件,避免弹窗消失
child: Container(
width: 240,
height: 160,
color: Colors.white,
alignment: Alignment.center,
child: Text(
'我是自定义的diaolg',
style: TextStyle(fontSize: 15,color: Colors.black,decoration: TextDecoration.none),
),
),
),
));
});
}
效果:
由于自定义了点击事件,点击外部阴影弹窗会消失,点击内部控件,可以根据需求作各种处理,然后内部的内容可以自主控制,灵活方便。
尾声
本来以为三篇基本上可以列出常用控件,现在看来还不够。接下来还有Drawer,BottomNavigationBar、NestedScrollView配合TabBar等做一些常见效果,后面再继续梳理。这篇笔记先写到这里。
以上。