布局
基本布局
- GridView(二維滾動列表):比ListView多了一個方向的數(shù)據(jù)填充。
- ListBody(滾動列表):相比ListView,沒有回收復(fù)用,簡單易用。
- Table(表格布局):子元素類似表格一樣在X軸和Y軸排列分布。
- Flow(流式布局):相比Wrap布局的子元素自動換行,Flow需要自行在Delegate里寫算法實現(xiàn)換行。
- Wrap(流式布局):子元素在X軸放得下就放,放不下就去下一行的布局。
- ScrollView(滾動視圖):一般搭配Column使用的類似滾動列表的布局。
GridView
- 就是二維的ListView
- 有五種構(gòu)造方式
- GridView()
- 默認構(gòu)造;
- 硬編碼子widget,數(shù)量多有性能風險
- GridVIew.builder()
- GridView.costom()
- GirdView.count()
- GridView.extent()
代碼
/// GridView()
GridView(gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,//交叉軸方向的數(shù)目),children: <Widget>[Text('1'),Text('2'),Text('3'),Text('4'),Text('5'),Text('6'),Text('7'),Text('8'),Text('9'),],
),
/// GridView.builder()
final List<String> name = <String>['1','2','3',];
GridView.builder(itemCount: name.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,//交叉軸方向item的數(shù)量mainAxisSpacing: 2.0,//主軸方向的間隔crossAxisSpacing: 2.0,//交叉軸之間的間隔),itemBuilder: (context, index) {return Container(child: Text(name[index]),);},
),
/// GridView.costom()GridView.custom(gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 80.0,//在交叉軸方向上單個item的最大長度crossAxisCount: 3,//交叉軸方向item的數(shù)量mainAxisSpacing: 2.0,//主軸方向的間隔),childrenDelegate: SliverChildBuilderDelegate((context, index) {return Container(child: Text(name[index]),);},childCount: name.length,),
),
/// GirdView.count()
GridView.count(crossAxisCount: 3,children: <Widget>[Text('1'),Text('2'),Text('3'),Text('4'),Text('5'),Text('6'),Text('7'),Text('8'),Text('9'),],
),
/// GridView.extent()
GridView.extent(maxCrossAxisExtent: 100.0,//在交叉軸方向上item的最大長度crossAxisCount: 3,//交叉軸方向item的數(shù)量mainAxisSpacing: 2.0,//主軸方向的間隔children: <Widget>[Container(child: Text("1"),),Container(child: Text("2"),),Container(child: Text("3"),),],
),
完整示例代碼
import 'package:flutter/material.dart';class HomeTabPage1 extends StatelessWidget {List listData = [{"title": "標題1","author": "內(nèi)容1","image": "https://www.itying.com/images/flutter/1.png"},{"title": "標題2","author": "內(nèi)容2","image": "https://www.itying.com/images/flutter/2.png"},{"title": "標題3","author": "內(nèi)容3","image": "https://www.itying.com/images/flutter/3.png"},{"title": "標題4","author": "內(nèi)容4","image": "https://www.itying.com/images/flutter/4.png"},{"title": "標題5","author": "內(nèi)容5","image": "https://www.itying.com/images/flutter/5.png"},{"title": "標題6","author": "內(nèi)容6","image": "https://www.itying.com/images/flutter/6.png"},{"title": "標題7","author": "內(nèi)容7","image": "https://www.itying.com/images/flutter/7.png"},{"title": "標題8","author": "內(nèi)容8","image": "https://www.itying.com/images/flutter/1.png"},{"title": "標題9","author": "內(nèi)容9","image": "https://www.itying.com/images/flutter/2.png"},{"title": "標題1","author": "內(nèi)容1","image": "https://www.itying.com/images/flutter/1.png"},{"title": "標題2","author": "內(nèi)容2","image": "https://www.itying.com/images/flutter/2.png"},{"title": "標題3","author": "內(nèi)容3","image": "https://www.itying.com/images/flutter/3.png"},{"title": "標題4","author": "內(nèi)容4","image": "https://www.itying.com/images/flutter/4.png"},{"title": "標題5","author": "內(nèi)容5","image": "https://www.itying.com/images/flutter/5.png"},{"title": "標題6","author": "內(nèi)容6","image": "https://www.itying.com/images/flutter/6.png"}];List<Widget> _getData() {List<Widget> list = [];for (var i = 0; i < listData.length; i++) {list.add(Container(child: Column(children: [Image.network(listData[i]["image"],fit: BoxFit.cover,),Text(listData[i]["title"],textAlign:TextAlign.center,)],),));}return list;}@overrideWidget build(BuildContext context) {return GridView.count(//設(shè)置滾動方向scrollDirection: Axis.vertical,//設(shè)置列數(shù)crossAxisCount: 5,//設(shè)置內(nèi)邊距(整個GridView的)padding: EdgeInsets.all(30),//設(shè)置橫向間距(3個間距一起用就能控制item各種距離了)crossAxisSpacing: 30,//設(shè)置主軸間距mainAxisSpacing: 30,children: _getData(),);}
}
常用參數(shù)
gridDelegate
- 有兩個實現(xiàn)類
- SliverGridDelegateWithFixedCrossAxisCount
- 根據(jù)設(shè)置的個數(shù)顯示二維滾動列表
- SliverGridDelegateWithMaxCrossAxisExtent
scrollDirection
* 滾動方向* Axis.vertical* 豎向滾動* Axis.horizontal* 橫向滾動
reverse
* 組件反向排序
controller
* 滾動監(jiān)聽
primary
* 值為false,內(nèi)容不足不可滑動
* 值為true,內(nèi)容不足可以嘗試滑動
shrinkWrap
* 內(nèi)容適配,默認為false
padding
* 內(nèi)邊距
crossAxisCount
* 列數(shù)
mainAxisSpacing
* 主軸之間的間距
crossAxisSpacing
* 橫軸之間的間距
childAspectRatio
* 設(shè)置寬高的比例
* GridView的子組件直接設(shè)置寬高沒有反應(yīng),可以通過childAspectRatio修改寬高
cacheExtent
* 設(shè)置預(yù)加載區(qū)域
children
* 組件元素 const Widget列表
* 數(shù)組內(nèi)添加widget類型的數(shù)據(jù)
* flutter的所有組件都是widget,也就是說所有的GridView可以添加所有的組件
semanticChildCount
* 提供語義信息的子組件數(shù)量
ListBody
- 很少單獨使用,搭配如Row、Column、ListView、Flex一起使用。
代碼
Column(//主軸垂直排列的列表,未限制寬,默認將充滿屏幕children: <Widget>[ListBody(//指定主軸方向與父框架相同mainAxis: Axis.vertical,reverse: false,//不反向children: <Widget>[Container(color: Colors.red, width: 50.0, height: 50.0),Container(color: Colors.yellow, width: 50.0, height: 50.0),Container(color: Colors.green, width: 50.0, height: 50.0),Container(color: Colors.blue, width: 50.0, height: 50.0),Container(color: Colors.black, width: 50.0, height: 50.0),],)],
)
Table
代碼
Container(width: 300.0,height: 200.0,padding: EdgeInsets.all(2.0),color: Color(0xFFC5CAE9),child: Table(//每行中單元格的寬度,TableRow內(nèi)元素個數(shù),即列數(shù),從第一個到最后一個的寬度//如果排列根據(jù)排列方向顯示不同columnWidths: const <int, TableColumnWidth>{0: FixedColumnWidth(30.0),1: FixedColumnWidth(70.0),2: FixedColumnWidth(50.0),3: FixedColumnWidth(100.0),},//默認未顯示寬度 默認的每一列寬度值,默認情況下均分。defaultColumnWidth: const FlexColumnWidth(1.0),//每個表格的排列方向,此處設(shè)置從右到左textDirection: TextDirection.rtl,//表格邊框,此處設(shè)置藍色,2像素寬,實線border: TableBorder.all(color: Colors.blue, width: 2.0, style: BorderStyle.solid),//每一個單元格的垂直方向的對齊方式,默認為頂部對齊defaultVerticalAlignment: TableCellVerticalAlignment.top,//基線類型,與TableCellVerticalAlignment.baseline一起使用//textBaseline: null,children: <TableRow>[TableRow(decoration: BoxDecoration(color: Colors.purpleAccent),children: <Widget>[Text('A1'),Text('A2'),Text('A3'),Text('A4'),],),TableRow(decoration: BoxDecoration(color: Colors.purpleAccent),children: <Widget>[Container(color: Colors.red, child: Text('赤')),Container(color: Colors.orange, child: Text('橙')),Container(color: Colors.yellow, child: Text('黃')),Container(color: Colors.green, child: Text('綠')),],),TableRow(children: <Widget>[Text('B1'),Text('B2'),Text('B3'),Text('B4'),]),],),
)
參數(shù)
- columnWidth:每列單元格的寬度,int為從0到每行的個數(shù)-1
- defaultColumnWidth:默認的每一列寬度值,默認情況下均分。
- textDirection:每列的排列方向,默認從左到右
- border:TableBorder 表格的邊框
- defaultVerticalAlignment:單元格默認垂直方向上的對齊方式,默認上對齊TableCellVerticalAlignment.top
- textBaseline:TableCellVerticalAlignment.baseline與此屬性配合使用。文本基線類型
- children:存放每行的單元格內(nèi)容的類別
Flow
FlowDelegate的方法
abstract class FlowDelegate {const FlowDelegate({ Listenable repaint }) : _repaint = repaint;final Listenable _repaint;//重寫設(shè)置尺寸Size getSize(BoxConstraints constraints) => constraints.biggest;//重寫設(shè)置約束BoxConstraints getConstraintsForChild(int i, BoxConstraints constraints) => constraints;//繪制children的位置和大小void paintChildren(FlowPaintingContext context);//是否要從新布局,可自己定制規(guī)則bool shouldRelayout(covariant FlowDelegate oldDelegate) => false;//是否從新繪制,可自己定制規(guī)則bool shouldRepaint(covariant FlowDelegate oldDelegate);@overrideString toString() => '$runtimeType';
}
代碼
Flow(delegate: TestFlowDelegate(margin: EdgeInsets.all(5.0)),children: <Widget>[new Container(width: 60.0, height: 60.0, color: Colors.red,child: Text('紅'), alignment: Alignment.center, ),new Container(width: 60.0, height: 60.0,color: Colors.orange,child: Text('橙'),alignment: Alignment.center, ),new Container(width: 60.0,height: 60.0,color: Colors.yellow,child: Text('黃'),alignment: Alignment.center, ),new Container(width: 60.0,height: 60.0,color: Colors.green,child: Text('綠'),alignment: Alignment.center, ),new Container(width: 60.0,height: 60.0,color: Colors.cyan,child: Text('青'),alignment: Alignment.center, ),new Container(width: 60.0, height: 60.0,color: Colors.blue,child: Text('藍'),alignment: Alignment.center, ),new Container(width: 60.0,height: 60.0,color: Colors.purple,child: Text('紫'),alignment: Alignment.center, ),],
)class TestFlowDelegate extends FlowDelegate {EdgeInsets margin = EdgeInsets.zero;TestFlowDelegate({this.margin});@overridevoid paintChildren(FlowPaintingContext context) {var x = margin.left;var y = margin.top;for (int i = 0; i < context.childCount; i++) {var w = context.getChildSize(i).width + x + margin.right;if (w < context.size.width) {context.paintChild(i,transform: new Matrix4.translationValues(x, y, 0.0));x = w + margin.left;} else {x = margin.left;y += context.getChildSize(i).height + margin.top + margin.bottom;context.paintChild(i,transform: new Matrix4.translationValues(x, y, 0.0));x += context.getChildSize(i).width + margin.left + margin.right;}}}@overridebool shouldRepaint(FlowDelegate oldDelegate) {return oldDelegate != this;}
}
Wrap
- 在mainAxis上空間不足時,則向crossAxis上去擴展顯示。
- Wrap使用方便一些,但是Flow能實現(xiàn)Wrap,Flow更強一些。
參數(shù)
- direction :主軸方向,默認水平
- alignment :主軸方向方式,值為WrapAlignment的枚舉值,詳情請看Row的對齊方式
- spacing :主軸方向上child之間的間距,默認為0
- runAlignment : 新一行或一列的對齊方式
- runSpacing :新的一行或一列的間距,默認為0
- crossAxisAlignment:交叉軸的對齊方式,默認是從主軸開始位置開始
- textDirection:每一行或一列的排列方式
- 如果一行有三個元素,則第一行取出前三個元素2,1,0這樣排列
- verticalDirection:垂直方向上排列方式,值為VerticalDirection的枚舉值,默認從上到下
代碼
Container(alignment: Alignment.topCenter,child: Wrap(//主軸方向,默認水平direction: Axis.horizontal,//主軸方向方式,包裹在一個控件內(nèi)效果明顯,默認主軸方向開始位置開始alignment: WrapAlignment.spaceBetween,//主軸方向上child之間的間距,默認為0spacing: 6.0,// 新一行或一列的對齊方式runAlignment: WrapAlignment.spaceBetween,//新的一行或一列的間距,默認為0runSpacing: 0.0,//交叉軸的對齊方式,默認是從主軸開始位置開始crossAxisAlignment: WrapCrossAlignment.start,//每一行或一列的排列方式,如果一行有三個元素,則第一行取出前三個元素2,1,0這樣排列//默認從左到右textDirection: TextDirection.ltr,//垂直方向上排列方式,默認從上到下verticalDirection: VerticalDirection.down,children: <Widget>[Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('1')),label: Text('Hamilton'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('2')),label: Text('Lafayette'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('3')),label: Text('Mulligan'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('4')),label: Text('Laurens'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('5')),label: Text('Hamilton'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('6')),label: Text('Lafayette'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('7')),label: Text('Mulligan'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('8')),label: Text('Laurens'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('9')),label: Text('Hamilton'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('10')),label: Text('Lafayette'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('11')),label: Text('Mulligan'),),Chip(avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: Text('12')),label: Text('Laurens'),),],),
)
ScrollView
SingleChildScrollView
代碼
SingleChildScrollView(child: Column(children: <Widget>[Container(height: 200,color: Colors.red,),Container(height: 200,color: Colors.green,),Container(height: 200,color: Colors.blue,),Container(height: 200,color: Colors.yellow,),Container(height: 200,color: Colors.orange,),],),
),
自定義ScrollPhysics控制滑動效果
- 準確的說這個屬于所有physics參數(shù)共用的一個案例
- 繼承ScrollPhysics可以實現(xiàn)自定義滑動的效果
import 'package:flutter/material.dart';void main() {runApp(MyApp());
}class MyCustomScrollPhysics extends ScrollPhysics {const MyCustomScrollPhysics({ScrollPhysics? parent}) : super(parent: parent);@overrideMyCustomScrollPhysics applyTo(ScrollPhysics? ancestor) {return MyCustomScrollPhysics(parent: buildParent(ancestor));}@overridedouble applyBoundaryConditions(ScrollMetrics position, double value) {// 檢查是否已經(jīng)滑動到邊界if (value < position.pixels && position.pixels <= position.minScrollExtent) {// 滑動到頂部邊界時,允許繼續(xù)向上滾動return 0.0;} else if (value > position.pixels && position.pixels >= position.maxScrollExtent) {// 滑動到底部邊界時,允許繼續(xù)向下滾動return 0.0;}// 其他情況,使用默認的邊界條件return super.applyBoundaryConditions(position, value);}
}class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(home: Scaffold(body: CustomScrollView(physics: MyCustomScrollPhysics(), // 使用自定義的ScrollPhysicsslivers: <Widget>[SliverAppBar(expandedHeight: 200.0,pinned: true,flexibleSpace: FlexibleSpaceBar(title: Text('Custom Scroll Physics Example'),),),SliverList(delegate: SliverChildBuilderDelegate((BuildContext context, int index) {return ListTile(title: Text('Item $index'),);},childCount: 100, // 你的列表項數(shù)量),),],),),);}
}