Flutter-从入门到放弃(源码,目录,持续更新中)
前面讲了ListView的四种构造方式及对应的使用场景,今天我们继续来学习一下ListView的嵌套使用。
实现UI的两种方式
有两种方式来实现我们的UI布局,第一种是在Column中使用两个ListView来展示上半部分的菜谱和下半部分的发帖内容,就像下图一样:
可以看出来,上半部分横向滑动,可以很好的展示出来,但是下半部分垂直方向滑动,能够为每条帖子留下的空间非常小。现在我们换一种ListView嵌套的方式来实现看看效果如何:
这种方式在一个ListView中嵌套了另外两个ListView,我们可以使用默认的构造器,直接声明两个子组件。这种实现方式的优点如下:
- 可滑动的区域更广
- 可以看到更多的发帖内容
- 可以在水平方向上滑动菜谱列表
- 当你向上滑动时,Flutter将监听到父ListView的滑动事件,菜谱列表和帖子都会向上滑动,可以有更多的空间来展示帖子。
这样看起来是不是更好呢,我们来试试吧。
ListView的嵌套使用
先打开explore_screen.dart,将build()方法的内容替换如下:
@override
Widget build(BuildContext context) {
return FutureBuilder(
//使用getExploreData()返回的future作为FutureBuilder的参数
future: mockService.getExploreData(),
//使用snapshot来查看Future的状态
builder: (context, AsyncSnapshot<ExploreData> snapshot) {
//如果Future已经完成,可以获取数据来更新UI
if (snapshot.connectionState == ConnectionState.done) {
return ListView(
//指定滑动方向
scrollDirection: Axis.vertical,
//声明子Widget
children: [
//上半部分的菜谱列表
TodayRecipeListView(recipes: snapshot.data?.todayRecipes ?? []),
//添加一个间距
const SizedBox(height: 16),
//TODO: Replace this with FriendPostListView
Container(
height: 400,
color: Colors.green,
)
],
);
} else {
//如果Future还没完成,则提示用户
return const Center(
child: CircularProgressIndicator(),
);
}
});
}
可以看出变化主要是将原来的菜谱列表替换为一个父ListView,内部准备嵌套两个子ListView,当然,下半部分的ListView我们还没完成,实际效果如下:
上下滑动时可以为上半部分或者下半部分留出更多的空间,左右滑动时又可以很好的展示菜谱列表,效果非常的棒,这个效果的实现真的比Android简单多了。。。
实现发帖ListView
首先我们把列表中的item进行实现,然后再来完成整个列表。每个帖子的效果如下:
在 lib/components里创建 friend_post_tile.dart.,添加如下代码:
import 'package:flutter/material.dart';
import '../components/components.dart';
import '../models/models.dart';
class FriendPostTile extends StatelessWidget {
final Post post;
const FriendPostTile({
Key? key,
required this.post,
}) : super(key: key);
@override
Widget build(BuildContext context) {
//1.创建一个Row Widget,它是一个水平布局的组件
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
//2.左边放置一个圆角图片
CircleImage(
imageProvider: AssetImage(post.profileImageUrl),
imageRadius: 20,
),
//3.添加一个间距
const SizedBox(width: 16),
//4.Expanded组件,让子组件填充剩余的空间
Expanded(
//5.Column组件,垂直方向的布局
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//6.帖子的内容
Text(post.comment),
//7.发帖的时间
Text(
'${post.timestamp} mins ago',
style: const TextStyle(fontWeight: FontWeight.w700),
),
],
),
),
],
);
}
}
注意,我们这里没有限制组件的高度,它不会受到高度限制,将会把所有的内容展示出来。
打开components.dart,添加如下代码:
export 'friend_post_tile.dart';
现在我们可以来实现发帖列表了。
在 lib/components中创建 friend_post_list_view.dart,添加如下代码:
import 'package:flutter/material.dart';
import '../models/models.dart';
import 'friend_post_tile.dart';
class FriendPostListView extends StatelessWidget {
//1.帖子的数据集合
final List<Post> friendPosts;
const FriendPostListView({
Key? key,
required this.friendPosts,
}) : super(key: key);
@override
Widget build(BuildContext context) {
//2.设置左右的padding
return Padding(
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: 0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//4.设置了一个标题
Text('Social Chefs ! ', style: Theme.of(context).textTheme.headline1),
//5.标题和列表之间的间距
const SizedBox(height: 16),
//6.列表的具体实现
ListView.separated(
//7.设置primary为false,告诉系统这不是一个主ListView
primary: false,
//8.设置不滚动,让父ListView来处理滚动事件
physics: const NeverScrollableScrollPhysics(),
//9.设置为true时将创建一个固定长度的可滚动集合
//设为false时会有一个an unbounded height 的错误
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: friendPosts.length,
itemBuilder: (context, index) {
//实际的item
final post = friendPosts[index];
return FriendPostTile(post: post);
},
separatorBuilder: (context, index) {
//item之间的间距
return const SizedBox(height: 16);
},
),
const SizedBox(height: 16),
],
),
);
}
}
同样在 components.dart 中导入该类:
export 'friend_post_list_view.dart';
现在我们进入 explore_screen.dart,把TODO: Replace this with FriendPostListView 提示下的 Container 替换为:
//下半部分的帖子列表
FriendPostListView(
friendPosts: snapshot.data?.friendPosts ?? []),
运行APP,体验一下效果吧。
你也可以在 main.dart中切换为 dark 模式来体验一下:
ListView的嵌套使用就完成了,下一节我们将会学习GridView的使用。