那个,Card常常做圆角背景,然后,Card和ListTile,是经常一起玩,ListTile可以试下各种丰富的item效果,因此ListTile经常和列表一起玩。
最后,说说九宫格和相对布局。
文中参阅了很多文章,感谢各位大佬。
不说了,开始吧。先来个Card
一、Card和ListTile
安卓里面,有CardView。Flutter为什么有Card,不言而喻了。
一.1、Card
Card的构造函数
* 卡片布局,相当于Android中的CardView
* const Card({
Key key,
this.color,//背景色
this.elevation,//阴影大小
this.shape,//设置边,可以设置圆角
this.margin = const EdgeInsets.all(4.0),
this.clipBehavior = Clip.none,
this.child,
this.semanticContainer = true,
})
-
key
相当于id -
color
颜色 -
elevation
阴影大小 -
shape
设置边,可以设置圆角 -
margin
外边距 -
clipBehavior
对Widget截取的行为,比如 Clip.antiAlias 指抗锯齿 -
semanticContainer
语义容器? 默认为true
例子
一个简单的例子,演示了边框,阴影的。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new CardSimple1()
);
}
}
class CardSimple1 extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Color.fromARGB(255, 66, 165, 245),
alignment: AlignmentDirectional(0.0, 0.0),
child: Container(
width: 200,
height: 200,
child: Card(
color: Colors.red,
// 普通的边
shape: Border.all(
color: Colors.yellow,
width: 5.0
),
elevation: 20,// 阴影大小
child: new Text("Card Widget"),
),
)
);
}
}
看呢,是看到了,但是不是我们熟悉的原角CardView。
来个圆角的
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new CardSimple1()
);
}
}
class CardSimple1 extends StatelessWidget{
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Color.fromARGB(255, 66, 165, 245),
alignment: AlignmentDirectional(0.0, 0.0),
child: Container(
width: 200,
height: 200,
child: Card(
color: Colors.red,
//设置圆角
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
// 普通的边
/* shape: Border.all(
color: Colors.yellow,
width: 5.0
),*/
elevation: 20,// 阴影大小
child: new Text("Card Widget"),
),
)
);
}
}
显而易见,我们通过RoundedRectangleBorder实现Card的圆角。
而且,Card里面的元素,居然显示在Card之外,这目前不知道怎么解决。
加上个抗锯齿吧 clipBehavior
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new CardSimple1());
}
}
class CardSimple1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Color.fromARGB(255, 66, 165, 245),
alignment: AlignmentDirectional(0.0, 0.0),
child: Card(
//设置圆角
// shape:
// RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0)),
color: Colors.purple,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)),),
// 普通的边
/* shape: Border.all(
color: Colors.yellow,
width: 5.0
),*/
// 抗锯齿
clipBehavior: Clip.antiAlias,
elevation: 20, // 阴影大小
child: new Container(
width: 200,
height: 200,
alignment: Alignment.center,
child: new Text("Card Widget",style: TextStyle(color: Colors.white),),
)),
);
}
}
指定角,实现圆角
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(50.0),
topRight: Radius.circular(50.0),
bottomLeft: Radius.zero,
bottomRight: Radius.zero),
),
一.2、ListTile
Card和ListTile,是经常一起玩。
ListTile的构造函数
const ListTile({
Key key,
this.leading,
this.title,
this.subtitle,
this.trailing,
this.isThreeLine = false,
this.dense,
this.contentPadding,
this.enabled = true,
this.onTap,
this.onLongPress,
this.selected = false,
})
-
key
相当于殴打 -
leading
将图像或图标添加到列表的开头。这通常是一个图标。 -
title
标题 -
subtitle
子标题 -
trailing
设置拖尾将在列表的末尾放置一个图像。这对于指示主-细节布局特别有用。(trailing本身是拖尾的意思) -
isThreeLine = false
默认为false 3行,当列表标题、副标题,有需要更多的空间来容纳长度超过一行的文本,可开启 -
dense
让文本变小 (dense本身是稠密的意思) -
contentPadding
内容的padding -
enabled = true
可否点击。可通过将 enable 设置为 false,来禁止点击事件 -
onTap
点击 -
onLongPress
长按 -
selected = false
是否选中,默认为否 ,如果选中列表的 item 项,那么文本和图标的颜色将成为主题的主颜色。
.
.
.
基本属性先用起来
这4个,先一起来,leading、title、subtitle、trailing
leading
将图像或图标添加到列表的开头。这通常是一个图标。
title
标题
subtitle
子标题
trailing
设置拖尾将在列表的末尾放置一个图像。这对于指示主-细节布局特别有用。(trailing本身是拖尾的意思)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new ListTileSimle());
}
}
class ListTileSimle extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Color.fromARGB(255, 66, 165, 245),
alignment: AlignmentDirectional(0.0, 0.0),
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0)),),
// 抗锯齿
clipBehavior: Clip.antiAlias,
elevation: 20,
// 阴影大小
child: new Container(
height: 100,
alignment: Alignment.center,
// 演示 ListTile
child: new ListTile(
title: new Text("海贼王"),
subtitle: new Text("来自东海的路飞"),
// item左侧的图像
leading: new Icon(
Icons.ac_unit,
color: Colors.blue[500],
),
// 列表尾部的图标
trailing: new Icon(Icons.chevron_right),
),
)),
);
}
}
其他属性的使用
-
isThreeLine
= false 默认为false 3行,当列表标题、副标题,有需要更多的空间来容纳长度超过一行的文本,可开启 -
dense
让文本变小 (dense本身是稠密的意思) -
contentPadding
内容的padding -
enabled = true
可否点击。可通过将 enable 设置为 false,来禁止点击事件 -
onTap
点击 -
onLongPress
长按 -
selected = false
是否选中,默认为否 ,如果选中列表的 item 项,那么文本和图标的颜色将成为主题的主颜色。
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: new ListTileSimle());
}
}
class ListTileSimle extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Color.fromARGB(255, 66, 165, 245),
alignment: AlignmentDirectional(0.0, 0.0),
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
// 抗锯齿
clipBehavior: Clip.antiAlias,
elevation: 20,
// 阴影大小
child: new Container(
height: 100,
alignment: Alignment.center,
// 演示 ListTile
child: new ListTile(
title: new Text("海贼王"),
subtitle: new Text("来自东海的路飞"),
// item左侧的图像
leading: new Icon(
Icons.ac_unit,
color: Colors.blue[500],
),
// 列表尾部的图标
trailing: new Icon(Icons.chevron_right),
isThreeLine:true,
dense: true, // 让文本变小
contentPadding:EdgeInsets.symmetric(horizontal: 20.0),
selected: true, // 如果选中列表的 item 项,那么文本和图标的颜色将成为主题的主颜色。
onTap: () { // 点击会有水波纹效果
// do something
},
onLongPress: (){
// do something else
},
),
)),
);
}
}
.
.
.
二 、列表 ListView
ListView,做列表呀。
ListView的属性
ListView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
常见属性
-
scrollDirection
Axis 设置滚动的方向,horizontal(水平)或vertical(垂直) - reverse
bool 是否翻转 -
itemExtent
double 滚动方向子控件的长度,垂直方向即为高度,水平方向即为宽度 -
controller
ScrollController 用来控制滚动位置及监听滚动事件 -
shrinkWrap
bool 是否根据子widget的总长度来设置ListView的长度 -
padding
EdgeInsetsGeometry 间距 -
children
List 子控件
primary
bool 当内容不足以滚动时,是否支持滚动;对于iOS系统还有一个效果:当用户点击状态栏时是否滑动到顶部。-
physics
ScrollPhysics:控制用户滚动视图的交互- AlwaysScrollableScrollPhysics:列表总是可滚动的。在iOS上会有回弹效果,在android上不会回弹。那么问题来了,如果primary设置为false(内容不足时不滚动),且 physics设置为AlwaysScrollableScrollPhysics,列表是否可以滑动?答案是可以,感兴趣的可以试一下
- PageScrollPhysics:一般是给PageView控件用的滑动效果。如果listview设置的话在滑动到末尾时会有个比较大的弹起和回弹
- ClampingScrollPhysics:滚动时没有回弹效果,同android系统的listview效果
- NeverScrollableScrollPhysics:就算内容超过列表范围也不会滑动
- BouncingScrollPhysics:不论什么平台都会有回弹效果
- FixedExtentScrollPhysics:不适用于ListView,原因:需要指定scroller为 - FixedExtentScrollController,这个scroller只能用于ListWheelScrollViews
shrinkWrap
: scroll view在滑动方向上的高度是否由内容高度决定,false:则高度为滑动方向上的最大允许高度;如果在滑动方向上没有设置约束,则这个字段必须设置为true,否则会报错。cacheExtent
:可见区域的前后会有一定高度的空间去缓存子控件,当滑动时就可以迅速呈现semanticChildCount
:有含义的子控件的数量,如ListView会用children的长度,ListView.separated会用children长度的一半
用于构造SliverChildListDelegate的属性
- addAutomaticKeepAlives:是否将子控件包裹在AutomaticKeepAlive控件内
- addRepaintBoundaries:true:是否将子控件包裹在 RepaintBoundary 控件内。用于避免列表滚动时的重绘,如果子控件重绘开销很小时,比如子控件就是个色块或简短的文字,把这个字段设置为false性能会更好
- addSemanticIndexes:是否把子控件包装在IndexedSemantics里,用来提供无障碍语义
来个简单例子吧
Flutter ListView 用法详解这个文章,根据构造方法不同,列举了几个场景,看起来,比较恰当,本文也是这么来。
方式1 默认构造函数(传入 List children) 少数子View
适用场景:已知有限个Item的情况下
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new ListViewSimle1(),
),
),
);
}
}
class ListViewSimle1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return new ListView(
//控制方向 默认是垂直的
scrollDirection: Axis.vertical,
children: <Widget>[
TileSimle(),
TileSimle(),
TileSimle(),
],
);
}
}
class TileSimle extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return Container(
color: Color.fromARGB(255, 66, 165, 245),
alignment: AlignmentDirectional(0.0, 0.0),
child: Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
// 抗锯齿
clipBehavior: Clip.antiAlias,
elevation: 20,
// 阴影大小
child: new Container(
height: 100,
alignment: Alignment.center,
// 演示 ListTile
child: new ListTile(
title: new Text("海贼王"),
subtitle: new Text("来自东海的路飞"),
// item左侧的图像
leading: new Icon(
Icons.ac_unit,
color: Colors.blue[500],
),
// 列表尾部的图标
trailing: new Icon(Icons.chevron_right),
),
)),
);
}
}
方式2 默认构造函数(传入 List children) 少数子View
适用场景:长列表时采用builder模式,能提高性能。不是把所有子控件都构造出来,而是在控件viewport加上头尾的cacheExtent这个范围内的子Item才会被构造。在构造时传递一个builder,按需加载是一个惯用模式,能提高加载性能。
就是类似安卓的ViewHolder。
简单例子1
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
// child: new ListViewSimle1(),
child: new ListView.builder(
itemBuilder: (context, index) => Text("Item $index"),
itemCount: 100),
),
),
);
}
}
构造多种样式的Item
abstract class ListItem {}
class HeadingItem implements ListItem {
final String heading;
HeadingItem(this.heading);
}
class MessageItem implements ListItem {
final String sender;
final String body;
MessageItem(this.sender, this.body);
}
ListView.builder(
itemBuilder: (context, index) {
final item = items[index];
if (item is HeadingItem) {
return ListTile(
title: Text(
item.heading,
style: Theme.of(context).textTheme.headline,
),
);
} else if (item is MessageItem) {
return ListTile(
title: Text(item.sender),
subtitle: Text(item.body),
);
}
},
itemCount: items.length))
方式3 separated 带分割线 dart
适用场景:列表中需要分割线时,可以自定义复杂的分割线
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
// child: new ListViewSimle1(),
child: ListView.separated(
itemBuilder: (context, index) {
return Text("Item $index");
},
separatorBuilder: (context, index) {
return Container(
color: Colors.grey,
height: 3,
);
},
itemCount: 100)
),
),
);
}
}
方式4 custom 自定义
适用场景:上面几种模式基本可以满足业务需求,如果你还想做一些其它设置(如列表的最大滚动范围)或获取滑动时每次布局的子Item范围,可以尝试custom模式
- 看看构造函数
const ListView.custom({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
this.itemExtent,
@required this.childrenDelegate,
double cacheExtent,
int semanticChildCount,
})
发现有一个是必须复写的 —— childrenDelegate
。
.
.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
// child: new ListViewSimle1(),
child: ListView.custom(childrenDelegate: CustomSliverChildDelegate())
),
),
);
}
}
class CustomSliverChildDelegate extends SliverChildDelegate {
/// 根据index构造child
@override
Widget build(BuildContext context, int index) {
// KeepAlive将把所有子控件加入到cache,已输入的TextField文字不会因滚动消失
// 仅用于演示
return KeepAlive(
keepAlive: true,
child: TextField(decoration: InputDecoration(hintText: '请输入')));
}
/// 决定提供新的childDelegate时是否需要重新build。在调用此方法前会做类型检查,不同类型时才会调用此方法,所以一般返回true。
@override
bool shouldRebuild(SliverChildDelegate oldDelegate) {
return true;
}
/// 提高children的count,当无法精确知道时返回null。
/// 当 build 返回 null时,它也将需要返回一个非null值
@override
int get estimatedChildCount => 100;
/// 预计最大可滑动高度,如果设置的过小会导致部分child不可见,设置报错
@override
double estimateMaxScrollOffset(int firstIndex, int lastIndex,
double leadingScrollOffset, double trailingScrollOffset) {
return 2500;
}
/// 完成layout后的回调,可以通过该方法获已完成布局的视图树包括哪些子控件
@override
void didFinishLayout(int firstIndex, int lastIndex) {
print('didFinishLayout firstIndex=$firstIndex firstIndex=$lastIndex');
}
}
参考:https://juejin.im/post/5cb1c9d5f265da037371777f
三 、列表 GridView
ListView都说完了,类似九宫格的GridView还会远吗
GridView的构造函数
GridView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
List<Widget> children = const <Widget>[],
int semanticChildCount,
})
常见属性
-
scrollDirection
Axis 设置滚动的方向,horizontal(水平)或vertical(垂直) -
reverse
bool 是否翻转 -
controlle
r ScrollController 用来控制滚动位置及监听滚动事件 -
shrinkWrap
bool 是否根据子widget的总长度来设置GridView的长度 -
padding
EdgeInsetsGeometry 间距 -
gridDelegate
SliverGridDelegate 控制子Widget如何进行布局 -
children
List 子控件
其实其他属性,已经没什么特别,需要重点看看的,也就是gridDelegate。
待会说。
GridView有好几种使用方式。
GridView的几种使用方式
方式1、GridView.count
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
// child: new ListViewSimle1(),
child: GridView.count(
// 方向,这行没设置页可以,默认就是垂直的
scrollDirection:Axis.vertical,
//水平子Widget之间间距
crossAxisSpacing: 10.0,
//垂直子Widget之间间距
mainAxisSpacing: 30.0,
//GridView内边距
padding: EdgeInsets.all(10.0),
//一行的Widget数量
crossAxisCount: 2,
//子Widget宽高比例
childAspectRatio: 2.0, // 比如2.0,就是 宽/高=2
//子Widget列表
children: getWidgetList(),
),
),
),
);
}
}
List<String> getDataList() {
List<String> list = [];
for (int i = 0; i < 100; i++) {
list.add(i.toString());
}
return list;
}
List<Widget> getWidgetList() {
return getDataList().map((item) => getItemContainer(item)).toList();
}
Widget getItemContainer(String item) {
return Container(
alignment: Alignment.center,
child: Text(
item,
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: Colors.blue,
);
}
如果设定了宽高比childAspectRatio,那么手动设定的宽高会失效
SliverGridDelegate的设置,直接在这种模式使用,但是我们结合build模式说,感觉合适一些。
方式2、GridView.build
这个,可以好好说下。
SliverGridDelegate,可以控制GridView的布局。
其中,有两个实现类
-
SliverGridDelegateWithMaxCrossAxisExtent
(创建一个具有交叉轴最大值的一个网格布局) -
SliverGridDelegateWithFixedCrossAxisCount
(设置交叉轴上子控件的个数)
分开说
实现类1 SliverGridDelegateWithMaxCrossAxisExtent
MaxCrossAxis
创建一个具有交叉轴最大值的一个网格布局,元素的行/列数不是一定的,动态设定的
- 对于SliverGridDelegateWithMaxCrossAxisExtent而言,水平方向元素个数不再固定
- 其水平个数也就是有几列,由 maxCrossAxisExtent 和屏幕的宽度以及padding和mainAxisSpacing等决定。
构造方法
const SliverGridDelegateWithMaxCrossAxisExtent({
@required this.maxCrossAxisExtent, //子控件的最大宽度,实际宽度是根据交叉轴的值进行平分,也就是说最大宽度并不一定是实际宽度,很有可能子控件的实际宽度要小于设置的最大宽度
this.mainAxisSpacing = 0.0, //主轴之间的间距
this.crossAxisSpacing = 0.0,//交叉轴之间的间距
this.childAspectRatio = 1.0,//子控件的宽高比
}
记住,最大特点是元素的行/列数不是一定的,动态设定的
.
.
- 当 maxCrossAxisExtent 为 50
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
List<String> datas = getDataList();
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
// child: new ListViewSimle1(),
child: GridView.builder(
// 方向,这行没设置页可以,默认就是垂直的
scrollDirection:Axis.vertical,
itemBuilder: (BuildContext context,int index){
return getItemContainer(datas[index]);
},
// MaxCrossAxis 其水平个数也就是有几列,由 maxCrossAxisExtent 和屏幕的宽度以及padding和mainAxisSpacing等决定。
gridDelegate:SliverGridDelegateWithMaxCrossAxisExtent(
//单个子Widget的水平最大宽度
maxCrossAxisExtent: 50,
//水平单个子Widget之间间距
mainAxisSpacing: 20.0,
//垂直单个子Widget之间间距
crossAxisSpacing: 10.0
),
),
),
),
);
}
}
List<String> getDataList() {
List<String> list = [];
for (int i = 0; i < 100; i++) {
list.add(i.toString());
}
return list;
}
List<Widget> getWidgetList() {
return getDataList().map((item) => getItemContainer(item)).toList();
}
Widget getItemContainer(String item) {
return Container(
alignment: Alignment.center,
child: Text(
item,
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: Colors.blue,
);
}
.
.
-
当 maxCrossAxisExtent 为 100
如果强制设置行/列数就不开心,怎么办?比如指定为4行,可以试试:
maxCrossAxisExtent: MediaQuery.of(context).size.width/4
通过MediaQuery.of(context).size.width就可得到屏幕的宽度,除以4,就是子控件的最大值,这样一来我们就可以确定要显示的子控件的个数了
其实使用了 MaxCrossAxis 还制定数量,感觉就是不合适的,有病的。
想指定数量,那么应该用 SliverGridDelegateWithFixedCrossAxisCount,人家说了FixedCrossAxisCount
.
实现类2 SliverGridDelegateWithMaxCrossAxisExtent
核心就是:crossAxisCount 指定数量
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
List<String> datas = getDataList();
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
// child: new ListViewSimle1(),
child: GridView.builder(
// 方向,这行没设置页可以,默认就是垂直的
scrollDirection:Axis.vertical,
itemBuilder: (BuildContext context,int index){
return getItemContainer(datas[index]);
},
// FixedCrossAxisCount 指定 行/列的数量,就垂直方向来说,指定行数
gridDelegate:SliverGridDelegateWithFixedCrossAxisCount(
//横轴元素个数: 3
crossAxisCount: 3,
//水平单个子Widget之间间距
mainAxisSpacing: 20.0,
//垂直单个子Widget之间间距
crossAxisSpacing: 10.0
),
),
),
),
);
}
}
List<String> getDataList() {
List<String> list = [];
for (int i = 0; i < 100; i++) {
list.add(i.toString());
}
return list;
}
List<Widget> getWidgetList() {
return getDataList().map((item) => getItemContainer(item)).toList();
}
Widget getItemContainer(String item) {
return Container(
alignment: Alignment.center,
child: Text(
item,
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: Colors.blue,
);
}
.
.
方式3、GridView.custom
这个也没什么特别的,之前ListView也有说过了,类似
- 构造函数
const GridView.custom({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
@required this.gridDelegate,
@required this.childrenDelegate,
double cacheExtent,
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})
看看构造函数,有两个必须复写的,简单示例下
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
List<String> datas = getDataList();
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
// child: new ListViewSimle1(),
child: GridView.custom(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, mainAxisSpacing: 10.0, crossAxisSpacing: 20.0, ),
childrenDelegate: SliverChildBuilderDelegate((context, position) {
return getItemContainer(datas[position]);
}, childCount: datas.length))
),
),
);
}
}
List<String> getDataList() {
List<String> list = [];
for (int i = 0; i < 100; i++) {
list.add(i.toString());
}
return list;
}
List<Widget> getWidgetList() {
return getDataList().map((item) => getItemContainer(item)).toList();
}
Widget getItemContainer(String item) {
return Container(
alignment: Alignment.center,
child: Text(
item,
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: Colors.blue,
);
}
参考:
https://blog.csdn.net/yuzhiqiang_1993/article/details/87968234
Flutter GridView
四、相对布局Stack
这货。堆叠的意思,跟安卓比,类似于相对布局,谁出现的晚,谁就可以出现在上方,覆盖别人。
看看构造函数
Stack({
Key key,
this.alignment = AlignmentDirectional.topStart,
this.textDirection,
this.fit = StackFit.loose,
this.overflow = Overflow.clip,
List<Widget> children = const <Widget>[],
})
-
alignment
:对齐方式,默认是左上角(topStart)。 -
textDirection
:文本的方向,绝大部分不需要处理。 -
fit
:定义如何设置non-positioned节点尺寸,默认为loose。
其中StackFit有如下几种:-
loose
:子节点宽松的取值,可以从min到max的尺寸; -
expand
:子节点尽可能的占用空间,取max尺寸; -
passthrough
:不改变子节点的约束条件。
-
-
overflow
:超过的部分是否裁剪掉(clipped)。
布局行为
Stack的布局行为,根据child是positioned还是non-positioned来区分。
- 对于positioned的子节点,它们的位置会根据所设置的top、bottom、right以及left属性来确定,这几个值都是相对于Stack的左上角;
- 对于non-positioned的子节点,它们会根据Stack的aligment(左上角)来设置位置。
对于绘制child的顺序,则是第一个child被绘制在最底端,后面的依次在前一个child的上面。调整先后出现,可以改变展示顺序。
例子
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
List<String> datas = getDataList();
@override
Widget build(BuildContext context) {
final title = 'list';
return new MaterialApp(
title: title,
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
//关键代码
var stack = new Stack(
alignment: const Alignment(0.0, 0.6), //分析 2
children: [
new CircleAvatar( //分析 3
backgroundImage: new AssetImage('images/lake.jpg'),
radius: 100.0,
),
new Container( //分析 4
decoration: new BoxDecoration(
color: Colors.black45,
),
child: new Text(
'添加水印',
style: new TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
],
);
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
elevation: 5.0,
),
body: Center( //分析 1
child: stack,
),
);
}
}
关于Stack,还有一个IndexedStack
,这里就不展开了。
参考:
Flutter 布局(八)- Stack、IndexedStack、GridView详解
https://www.jianshu.com/p/f1b8fbe5cda0
.
.
.
END