Flutter学习记录——9.列表滚动
  Xa7AsmJIfSXT 2023年11月02日 80 0


文章目录

1.CustomScrollView Widget

CustomScrollView 是一个可以自己通过 Flutter 里的 sliver 来组装滚动 Widget 的一个控件,里面可以放置任何我们需要滚动的 Widget,也是相对来说最常使用的一个滚动组件。

我们看下 CustomScrollView 的继承关系:

CustomScrollView -> ScrollView -> StatelessWidget

CustomScrollView 是一个无状态组件,继承自 ScrollView,也扩展了 ScrollView 的功能。

CustomScrollView 最大的特点就是内部的组装的滚动组件都是 Sliver 特性的,也就是必须是 Sliver 可滚动块的 Widget 才可以,如:CustomScrollView 内部可以放置 SliverList、SliverFixedExtentList、SliverGrid、SliverPadding、SliverAppBar、SliverToBoxAdapter 等组件。

CustomScrollView 非常强大,如我们可以把 ListView 和 GridView 组装在一起,也可以拼装其他的 Sliver Widget,实现更复杂的效果。

我们看下 CustomScrollView 的构造方法:

const CustomScrollView({
Key key,
// 滚动方向
Axis scrollDirection = Axis.vertical,
// 是否反向显示
bool reverse = false,
// 滚动控制对象
ScrollController controller,
// 是否是与父级关联的主滚动视图
bool primary,
// 滚动视图应如何响应用户输入
ScrollPhysics physics,
// 是否根据正在查看的内容确定滚动视图的范围
bool shrinkWrap = false,
Key center,
double anchor = 0.0,
// 缓存区域
double cacheExtent,
// 内部sliver组件
this.slivers = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})

接下来我们通过一个实例来看下如何使用 CustomScrollView:

@override
Widget build(BuildContext context) {
return Material(
child: CustomScrollView(
// slivers里面放置sliver滚动块组件
slivers: <Widget>[
// 放置一个顶部的标题栏
SliverAppBar(
// 是否固定在顶部
pinned: true,
// 展开高度
expandedHeight: 250.0,
// 可展开区域,通常是一个FlexibleSpaceBar
flexibleSpace: FlexibleSpaceBar(
title: const Text('CustomScrollView'),
background: Image.asset("assets/image_appbar.jpg",fit: BoxFit.cover,),
),
),
// 放置一个SliverGrid Widget
SliverGrid(
// 设置Grid属性:
// SliverGridDelegateWithMaxCrossAxisExtent:
// 按照设置最大扩展宽度计算item个数
// SliverGridDelegateWithFixedCrossAxisCount:
// 可以固定设置每行item个数
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
// item最大宽度
maxCrossAxisExtent: 200.0,
// 主轴item间隔
mainAxisSpacing: 10.0,
// 交叉轴item间隔
crossAxisSpacing: 10.0,
// item宽高比
childAspectRatio: 4.0,
),
// 设置item的布局及属性
// SliverChildListDelegate:适用于有固定数量的item的List
// SliverChildBuilderDelegate:适用于不固定数量的item的List
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text('grid item $index'),
);
},
// 20个item数量
childCount: 20,
),
),
// 指定item高度的List
SliverFixedExtentList(
// item固定高度
itemExtent: 50.0,
// 设置item布局和属性
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('list item $index'),
);
},
childCount: 20,
),
),
],
),
);
}

运行效果如图:

Flutter学习记录——9.列表滚动_ide

接下来再给一个稍微复杂点的实例:

ScrollController _scrollController =
ScrollController(initialScrollOffset: 5, keepScrollOffset: true);

Widget customScrollview1() {
_scrollController.addListener(() {
///滚动监听
});
return CustomScrollView(
shrinkWrap: false,
primary: false,
// 回弹效果
physics: BouncingScrollPhysics(),
scrollDirection: Axis.vertical,
controller: _scrollController,
slivers: <Widget>[
///SliverGrid用法
SliverGrid(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.teal[100 * (index % 9)],
child: Text(
'grid item $index',
style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
),
);
},
childCount: 20,
),

///设置Grid属性:
///SliverGridDelegateWithMaxCrossAxisExtent:
///按照设置最大扩展宽度计算item个数
///SliverGridDelegateWithFixedCrossAxisCount:
///可以固定设置每行item个数
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5,
mainAxisSpacing: 10,
crossAxisSpacing: 10,

///item高度缩放比例,默认为1;小于1表示放大,大于1表示缩小
childAspectRatio: 1,
),
// SliverGridDelegateWithMaxCrossAxisExtent(
// ///item最大宽度
// maxCrossAxisExtent: 400.0,
// mainAxisSpacing: 10.0,
// crossAxisSpacing: 10.0,
// childAspectRatio: 4.0,
// ),
),

///SliverChildListDelegate:适用于有固定数量的item的List
///SliverChildBuilderDelegate:适用于不固定数量的item的List

SliverList(
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text(
'SliverList item $index',
style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
),
);
}, childCount: 20),
),
// 可以伸缩滚动的头部
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
minHeight: 60.0,
maxHeight: 180.0,
child: Container(
color: Colors.pink,
child: Image.asset("assets/image_appbar.jpg",fit: BoxFit.cover,),
),
),
),

///指定item高度的List
SliverFixedExtentList(
///item固定高度
itemExtent: 50,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text(
'list item $index',
style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
),
);
},
childCount: 20,
),
),
SliverList(
delegate: SliverChildListDelegate(<Widget>[
Text(
"SliverList SliverChildListDelegate",
style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
),
Image.asset("assets/flutter-mark-square-64.png"),
]),
),
// SliverPadding周围可以设置内边距
SliverPadding(
padding: const EdgeInsets.all(10.0),
sliver: SliverList(
delegate: SliverChildListDelegate(<Widget>[
Text(
"SliverPadding SliverChildListDelegate",
style: TextStyle(fontSize: 12, decoration: TextDecoration.none),
),
Image.asset("assets/flutter-mark-square-64.png"),
]),
),
),
// SliverToBoxAdapter内部可以放置任意Widget
SliverToBoxAdapter(
child: Text(
"SliverToBoxAdapter",
style: TextStyle(fontSize: 16, decoration: TextDecoration.none),
),
),
],
);
}

运行效果如图:

Flutter学习记录——9.列表滚动_flutter_02

2.ListView Widget

接下来我们看下 ListView 组件用法,很简单,ListView 主要实现线性列表布局,可以横向或者纵向。先看下ListView 的继承关系:

ListView -> BoxScrollView -> ScrollView

ListView 也是继承自 ScrollView 组件,扩展了 ScrollView 的特点。

看下 ListView 的构造方法:

ListView({
Key key,
// 滚动排列方向
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
// 物理滑动响应动画
ScrollPhysics physics,
// 是否根据子widget的总高度/长度来设置ListView的长度,默认值为false
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
// item固定高度
this.itemExtent,
// 是否将item包裹在AutomaticKeepAlive widget中
bool addAutomaticKeepAlives = true,
// 是否将item包裹在RepaintBoundary中
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
// 子item元素
List<Widget> children = const <Widget>[],
int semanticChildCount,
DragStartBehavior dragStartBehavior = DragStartBehavior.down,
})

接下来通过一个实例来看下 ListView 的用法:

//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'),
],
),

// 稍复杂用法
// 定义一个List
List<String> items = <String>[
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
];

// 定义个枚举来设置item显示几行及类型
enum _MaterialListType {
oneLine,

oneLineWithAvatar,

twoLine,

threeLine,
}

class ListViewSamples extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return ListViewSamplesState();
}
}

class ListViewSamplesState extends State<ListViewSamples> {
List widgets = [];

@override
void initState() {
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView'),
),
body: listView4(),
);
}

///最简单的ListView
Widget listView1() {
return ListView(
children: <Widget>[
Text(
'data',
style: TextStyle(fontSize: 30),
),
Text(
'data',
style: TextStyle(fontSize: 30),
),
Text(
'data',
style: TextStyle(fontSize: 30),
),
Text(
'data',
style: TextStyle(fontSize: 30),
),
Text(
'data',
style: TextStyle(fontSize: 30),
),
Text(
'data',
style: TextStyle(fontSize: 30),
),
Text(
'data',
style: TextStyle(fontSize: 30),
),
],
);
}

///动态封装ListView,使用ListTile作为item
Widget listView2() {
// listTiles为item布局集合
Iterable<Widget> listTiles = items.map<Widget>((String string) {
return getItem(string);
});
ListTile.divideTiles(context: context, tiles: listTiles);
return ListView(
children: listTiles.toList(),
);
}

///使用ListView.builder构造
Widget listView3() {
// item widget集合
return ListView.builder(
// 设置item数量
itemCount: items.length,
itemBuilder: (BuildContext context, int position) {
return getItem(items.elementAt(position));
},
);
}

///ListView.custom构建ListView
Widget listView4() {
///SliverChildListDelegate:适用于有固定数量的item的List
///SliverChildBuilderDelegate:适用于不固定数量的item的List
return ListView.custom(
// 设置item构建属性
childrenDelegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
// 返回item布局
return ListTile(
isThreeLine: true,
dense: true,
leading: ExcludeSemantics(
child: CircleAvatar(child: Text(items.elementAt(index)))),
title: Text('This item represents .'),
subtitle: Text("$index"),
trailing: Icon(Icons.info, color: Theme.of(context).disabledColor),
);
}, childCount: 13),
);
}

///ListView.separated构建ListView
Widget listView5() {
// 有分隔线
return ListView.separated(
// item数量
itemCount: items.length,
// 分隔线属性设置
separatorBuilder: (BuildContext context, int index) {
return Container(height: 1, color: Colors.pink);
},
// 构建item布局
itemBuilder: (BuildContext context, int index) {
return ListTile(
isThreeLine: true,
dense: true,
leading: ExcludeSemantics(
child: CircleAvatar(child: Text(items.elementAt(index)))),
title: Text('This item represents .'),
subtitle: Text(items.elementAt(index)),
trailing: Icon(Icons.info, color: Theme.of(context).disabledColor),
);
},
);
}

// 用来获取item布局的方法
Widget getItem(String item) {
// if (i.isOdd) {
// return Divider();
// }
return GestureDetector(
child: Padding(
padding: EdgeInsets.all(10.0),
child: ListTile(
dense: true,
title: Text('Two-line ' + item),
trailing: Radio<_MaterialListType>(
value: _MaterialListType.twoLine,
groupValue: _MaterialListType.twoLine,
onChanged: changeItemType,
)),
),
onTap: () {
setState(() {
// print('row $i');
});
},
onLongPress: () {},
);
}

void changeItemType(_MaterialListType type) {
print("changeItemType");
}
}

运行效果如图:

Flutter学习记录——9.列表滚动_flutter_03

3.GridView Widget

GridView 的用法和 ListView 类似,可以对比着学习,就是实现网格列表的 item 排列效果。先看下 GridView 的继承关系:

GridView -> BoxScrollView -> ScrollView

GridView 也是继承自 ScrollView 组件,扩展了 ScrollView 的特点。

看下 GridView 的构造方法:

GridView({
Key key,
Axis scrollDirection = Axis.vertical,
bool reverse = false,
ScrollController controller,
bool primary,
ScrollPhysics physics,
bool shrinkWrap = false,
EdgeInsetsGeometry padding,
// 控制GridView的item如何排列
@required this.gridDelegate,
bool addAutomaticKeepAlives = true,
bool addRepaintBoundaries = true,
bool addSemanticIndexes = true,
double cacheExtent,
// item集合
List<Widget> children = const <Widget>[],
int semanticChildCount,
})

我们通过一个实例来看下 GridView 用法:

//GridView简单的用法
body: GridView.count(
primary: false,
padding: const EdgeInsets.all(20.0),
crossAxisSpacing: 10.0,
// 每行多少个item
crossAxisCount: 2,
children: <Widget>[
const Text('He\'d have you all unravel at the'),
const Text('Heed not the rabble'),
const Text('Sound of screams but the'),
const Text('Who scream'),
const Text('Revolution is coming...'),
const Text('Revolution, they...'),
],
),

// 稍微复杂点用法
class GridViewSamplesState extends State<GridViewSamples> {
@override
void initState() {
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GridView'),
backgroundColor: Colors.teal,
primary: true,
),
body: gridView1(),
);
}
// 构造GriView方式1
Widget gridView1() {
return GridView(
///设置Grid属性:
///SliverGridDelegateWithMaxCrossAxisExtent:
///按照设置最大扩展宽度计算item个数
///SliverGridDelegateWithFixedCrossAxisCount:
///可以固定设置每行item个数
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
children: <Widget>[
Image.asset(
'assets/image_appbar.jpg',
fit: BoxFit.cover,
),
Image.asset(
'assets/image_appbar.jpg',
fit: BoxFit.cover,
),
Image.asset(
'assets/image_appbar.jpg',
fit: BoxFit.cover,
),
Image.asset(
'assets/image_appbar.jpg',
fit: BoxFit.cover,
),
Image.asset(
'assets/image_appbar.jpg',
fit: BoxFit.cover,
),
],
);
}
// 构造GriView方式2
Widget gridView2() {
return GridView.builder(
// item总数
itemCount: 20,
// 构建item
itemBuilder: (BuildContext context, int index) {
// GridTile可以构造带有头部、底部、中间内容的item
return GridTile(
header: GridTileBar(
title: Text(
'header',
style: TextStyle(fontSize: 20),
),
backgroundColor: Colors.black45,
leading: Icon(
Icons.star,
color: Colors.white,
),
),
child: Image.asset('assets/image_appbar.jpg'),
footer: GridTileBar(
title: Text(
'bottom',
style: TextStyle(fontSize: 20),
),
backgroundColor: Colors.black45,
),
);
},
// GridView排列属性设置
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
),
);
}
// 构造GriView方式3
Widget gridView3() {
return GridView.custom(
// 设置GridView属性
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 2,
),
// 设置item属性
childrenDelegate:
SliverChildBuilderDelegate((BuildContext context, int index) {
return Container(
child: Text(
'GridTile',
style: TextStyle(fontSize: 16),
),
);
}, childCount: 20),
);
}
// 构造GriView方式4
Widget gridView4() {
return GridView.count(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 1,
children: <Widget>[
GridTile(
child: Image.asset('assets/image_appbar.jpg'),
),
GridTile(
child: Image.asset('assets/image_appbar.jpg'),
),
GridTile(
child: Image.asset('assets/image_appbar.jpg'),
),
GridTile(
child: Image.asset('assets/image_appbar.jpg'),
),
GridTile(
child: Image.asset('assets/image_appbar.jpg'),
),
],
);
}

// 构造GriView方式5
///GridView.extent构建GridView,根据最大宽度自动计算item数量
Widget gridView5() {
return GridView.extent(
// item最大宽度
maxCrossAxisExtent: 150,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
childAspectRatio: 1,
children: <Widget>[
GridTile(
header: GridTileBar(
title: Text(
'header',
style: TextStyle(fontSize: 20),
),
backgroundColor: Colors.black45,
leading: Icon(
Icons.star,
color: Colors.white,
),
),
child: Image.asset('assets/image_appbar.jpg'),
footer: GridTileBar(
title: Text(
'bottom',
style: TextStyle(fontSize: 20),
),
backgroundColor: Colors.black45,
),
),
GridTile(
header: GridTileBar(
title: Text(
'header',
style: TextStyle(fontSize: 20),
),
backgroundColor: Colors.black45,
leading: Icon(
Icons.star,
color: Colors.white,
),
),
child: Image.asset('assets/image_appbar.jpg'),
footer: GridTileBar(
title: Text(
'bottom',
style: TextStyle(fontSize: 20),
),
backgroundColor: Colors.black45,
),
),
],
);
}
}

运行效果如图:

Flutter学习记录——9.列表滚动_Flutter_04

4.ScrollView

ScrollView 这个 Widget 并不直接单独使用,一般是使用它的扩展类,如 CustomScrollView、ListView、GridView 等等。这里将他们相关的通用特性讲一下:

滚动条样式的设置。一般滚动类组件滚动时,我们希望侧边显示一个滚动条,那么我们可以使用 Scrollbar 进行包裹:

///Scrollbar
Widget scroll1() {
return Scrollbar(
child: ListView.separated(
itemCount: 20,
separatorBuilder: (BuildContext context, int index) {
return Container(height: 1, color: Colors.black87);
},
itemBuilder: (BuildContext context, int index) {
return ListTile(
isThreeLine: true,
dense: true,
leading:
ExcludeSemantics(child: CircleAvatar(child: Text('leading'))),
title: Text('This item represents .'),
subtitle: Text('subtitle'),
trailing: Icon(Icons.info, color: Theme.of(context).disabledColor),
);
},
),
);
}

单子元素的 ScrollView(SingleChildScrollView),类似于 Android 里的 ScrollView,内部只能包裹一个子控件:

///SingleChildScrollView
Widget scroll2() {
// 只能放置一个元素
return SingleChildScrollView(
child: Column(
children: <Widget>[
Container(
// A fixed-height child.
color: Colors.yellow,
height: 620.0,
),
Container(
color: Colors.orange,
height: 720.0,
),
],
),
);
}

有的时候我们需要自定义放置在内部的子元素排列方式,可以使用 CustomMultiChildLayout 或 CustomSingleChildLayout,这两个不常用,大家了解即可:

// CustomMultiChildLayout
Widget scroll5() {
return CustomMultiChildLayout(
delegate: MultiChildDelegate(),
children: <Widget>[
LayoutId(
id: MultiChildDelegate.title,
child: Container(
color: Colors.teal,
child: Text('data1'),
),
),
LayoutId(
id: MultiChildDelegate.description,
child: Container(
color: Colors.amber,
child: Text('data2'),
),
),
],
);
}

class MultiChildDelegate extends MultiChildLayoutDelegate {
static const String title = 'title';
static const String description = 'description';

@override
void performLayout(Size size) {
///约束
BoxConstraints boxConstraints = BoxConstraints(maxWidth: size.width);

///绑定约束
Size titleSize = layoutChild(title, boxConstraints);

///位置
positionChild(title, Offset(0, 0));
Size descriptionSize = layoutChild(description, boxConstraints);
double descriptionY = titleSize.height;
positionChild(description, Offset(10, descriptionY));
}

@override
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) {
return oldDelegate == this;
}
}

// CustomSingleChildLayout
Widget scroll6() {
return CustomSingleChildLayout(
delegate: SingleChildDelegate(Size(300, 200)),
child: Container(
color: Colors.teal,
child: Text('data'),
),
);
}

class SingleChildDelegate extends SingleChildLayoutDelegate {
Size size;
SingleChildDelegate(this.size);

@override
Size getSize(BoxConstraints constraints) {
// return super.getSize(constraints);
return size;
}

@override
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
// return super.getConstraintsForChild(constraints);
return BoxConstraints.tight(size);
}

@override
Offset getPositionForChild(Size size, Size childSize) {
// return super.getPositionForChild(size, childSize);
return Offset(30, 20);
}

@override
bool shouldRelayout(SingleChildLayoutDelegate oldDelegate) {
return oldDelegate == this;
}
}

5.ExpansionPanel Widget

ExpansionPanel 用来实现类似于 QQ 分组的一个组件,一般搭配 ExpansionPanelList 一起使用。

ExpansionPanelList 继承自 StatefulWidget,是有状态组件。

我们接下来看下 ExpansionPanelList 的构造方法:

const ExpansionPanelList({
Key key,
// 子元素ExpansionPanel集合
this.children = const <ExpansionPanel>[],
// 展开/关闭回调
this.expansionCallback,
// 展开动画执行时长
this.animationDuration = kThemeAnimationDuration,
})

然后是 ExpansionPanel 的构造方法:

ExpansionPanel({
// 标题构造器
@required this.headerBuilder,
// 内容区域
@required this.body,
// 是否展开
this.isExpanded = false
})

接下来通过一个实例来看下 ExpansionPanelList 和 ExpansionPanel 的用法:

class ExpansionPanelListPageState extends State<ExpansionPanelListPage> {
List<ExpansionPanelItem> _expansionPanelItems;
@override
void initState() {
super.initState();
getExpansionPanelList();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ExpansionPanelList"),
),
body: Container(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
// ExpansionPanelList
ExpansionPanelList(
// 动态创建ExpansionPanel item布局
children: _expansionPanelItems.map((ExpansionPanelItem item) {
return ExpansionPanel(
// 头部区域
headerBuilder: (BuildContext context, bool isExpanded) {
return Container(
padding: EdgeInsets.all(10.0),
child: Text(
item.headerText,
style: Theme.of(context).textTheme.title,
),
);
},
// 内容区域
body: item.body,
// 面板是否展开
isExpanded: item.isExpand,
);
}).toList(),
// 面板展开/关闭回调
expansionCallback: (int panelIndex, bool isExpanded) {
setState(() {
_expansionPanelItems[panelIndex].isExpand = !isExpanded;
});
},
)
],
),
),
);
}
// 创建面板item布局的集合
List<ExpansionPanelItem> getExpansionPanelList() {
_expansionPanelItems = new List();
for (int i = 0; i < 5; i++) {
_expansionPanelItems.add(ExpansionPanelItem(
headerText: 'Panel B',
body: Container(
padding: EdgeInsets.all(10.0),
width: double.infinity,
child: Text('Content for Panel $i),
),
isExpand: false,
));
}
_expansionPanelItems.length = 5;
return _expansionPanelItems;
}
}

// 自定义item
class ExpansionPanelItem {
final String headerText;
final Widget body;
bool isExpand;
ExpansionPanelItem({
this.headerText,
this.body,
this.isExpand,
});
}

// 简单用法

class ExpansionPanelPageState extends State<ExpansionPanelPage> {
// 面板是否展开状态保存
bool _isExpanded = false;

@override
void initState() {
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ExpansionPanelList"),
),
body: Container(
padding: EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// ExpansionPanelList
ExpansionPanelList(
children: <ExpansionPanel>[
// ExpansionPanel
ExpansionPanel(
// 头部
headerBuilder: (BuildContext context, bool isExpanded) {
return Container(
padding: EdgeInsets.all(10.0),
child: Text(
'Panel A',
style: Theme.of(context).textTheme.title,
),
);
},
// 展开的内容区域
body: Container(
padding: EdgeInsets.all(10.0),
width: double.infinity,
child: Text('Content for Panel A\n The Content'),
),
// 是否展开
isExpanded: _isExpanded,
),
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return Container(
padding: EdgeInsets.all(10.0),
child: Text(
'Panel B',
style: Theme.of(context).textTheme.title,
),
);
},
body: Container(
padding: EdgeInsets.all(10.0),
width: double.infinity,
child: Text('Content for Panel B\n The Content'),
),
isExpanded: _isExpanded,
)
],
// 展开或关闭回调
expansionCallback: (int panelIndex, bool isExpanded) {
setState(() {
_isExpanded = !isExpanded;
});
},
)
],
),
),
);
}
}

Flutter学习记录——9.列表滚动_滚动视图_05

ExpansionPanelList 和 ExpansionPanel 并不属于滚动组件,只不过看起来类似。这里放在一起顺便讲解。

6.总结

本节博客主要是给大家讲解了 Flutter 的几个常用的列表、滚动布局组件 Widget 的用法和特点。主要注意点和建议如下:

  • 重点掌握 CustomScrollView、ListView 和 GridView 用法。
  • 实践一下这几个 Widget 使用方法,尝试写一个可以滚动的列表页面。


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  b1UHV4WKBb2S   2023年11月13日   40   0   0 ide抗锯齿
  b1UHV4WKBb2S   2023年11月13日   34   0   0 裁剪ideflutter
  b1UHV4WKBb2S   2023年11月13日   27   0   0 flutterDart
  zSWNgACtCQuP   2023年11月13日   32   0   0 ide
Xa7AsmJIfSXT