先上图:
douiyin.gif
2.上滑时,没有加载的,会进行加载
douiyin2.gif
分析
1.打开详情页面,带入id
2.请求第一页数据(应该传入id),响应数据应该返回id对应的索引 如:{id:9,index:78}
3.点击“查看刚刚视频”按钮,判断index在哪一页
4.根据在哪一页,计算出需要创建的空白items,
5.跳转到刚刚id对应的index,同时请求id对应页码的数据
6.拿到数据之后,进行数据覆盖
7.监听上下滑动如果没有加载数据的页面,请求加载
8.抖音的页面,下拉时,如果上一页没有加载,会触发下拉事件来,请求对应页码数据--这里暂时不做
这里是跳转的代码,使用了一个跳转库scrollview_observer,传送门https://pub.dev/packages/scrollview_observer/example
class VideoItemEntity {
int id = 0;
String title = '';
String thumbUrl = '';
String? videoUrl = '';
int? duration = 0;
int? playCount = 0;
int? likeCount = 0;
int? commentCount = 0;
int? shareCount = 0;
VideoItemEntity(
int this.id,
String this.title,
String this.thumbUrl, {
String? this.videoUrl,
int? this.duration,
int? this.playCount,
int? this.likeCount,
int? this.commentCount,
int? this.shareCount,
});
}
import 'package:flutter/material.dart';
import 'package:flutter_test_demo/bean/video_item_entity.dart';
import 'package:scrollview_observer/scrollview_observer.dart';
class DouYinLastView extends StatefulWidget {
int? lastViewId = 0;
DouYinLastView({super.key, this.lastViewId});
@override
State<DouYinLastView> createState() => _DouYinLastViewState();
}
class _DouYinLastViewState extends State<DouYinLastView> {
List<VideoItemEntity> list = [];
int currentPage = 1;
int pageSize = 20;
int total = 100;
int columnCount = 3;
double mainSpacing = 2;
double crossSpacing = 2;
double aspectRatio = 0.9;
ScrollController _scrollController = ScrollController();
late GridObserverController observerController;
List<VideoItemEntity> allList = []; // 模拟数据库中所有数据
int lastViewIdIndex = -1;
List<int> loadedPages = []; // 已经加载过的页面
@override
void initState() {
super.initState();
observerController = GridObserverController(controller: _scrollController);
allData();
initData();
}
allData() {
var imgUrl = "https://photo.tuchong.com/424887/f/10370407.jpg";
allList = List.generate(100, (index) {
return VideoItemEntity(index + 1, "第${index + 1}个视频", imgUrl);
});
}
initData() async {
// 模拟请求数据
var data = await getPageData(currentPage);
setState(() {
list.addAll(data);
});
;
}
void scrollToItem(int index) async {
// 1.索引在第几页 ,这里索引需要+1,表明是第几项
int page = ((index+1)/ pageSize).ceil();
// 2.当前页码是否加载过
bool isLoaded = loadedPages.contains(page);
if (!isLoaded) addEmptyData(page);
// 3.加载空数据 需要延时一下
await Future.delayed(Duration(milliseconds: 100));
// 4.跳转
observerController.animateTo(
index: index, duration: Duration(seconds: 1), curve: Curves.easeInOut);
// 5.更新对应页码的数据,
if (!isLoaded) updatePageData(page);
}
addEmptyData(int page) {
// print("page ${page}");
int diff = page - currentPage;
if (diff != 0) {
// 2.计算需要添加的占位item数量
int addItems = diff * pageSize;
setState(() {
for (int i = 0; i < addItems; i++) {
list.add(
VideoItemEntity(list.last.id + 1, "新增的 ${list.last.id + 1}", ''));
}
});
}
}
updatePageData(int page) async {
var data = await getPageData(page);
// 假设请求成功,更新数据
int startIndex = (page - 1) * pageSize;
int endIndex = startIndex + pageSize;
setState(() {
// 假设请求成功,更新数据
for (int i = startIndex; i < endIndex; i++) {
list[i] = data[i - startIndex];
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("仿抖音'刚刚看过'功能")),
body: Stack(children: [
GridViewObserver(
child: grids(),
controller: observerController,
onObserve: (resultModel) {
print(
'firstChild.index -- ${resultModel.firstGroupChildList?.first.index}');
print('displaying -- ${resultModel.displayingChildIndexList}');
var index = resultModel.firstGroupChildList?.first.index;
var page = (index! / pageSize).ceil();
// 滑动的时候可以查看当前页面是否有加载数据
if (!loadedPages.contains(page)) {
updatePageData(page);
}
},
),
if (lastViewIdIndex != -1)
Positioned(
bottom: 5,
right: 5,
child: InkWell(
onTap: () {
scrollToItem(lastViewIdIndex);
},
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(10)),
child: Text(
'刚刚看过»',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
)
]),
);
}
grids() {
return GridView.count(
controller: _scrollController,
shrinkWrap: true,
crossAxisCount: columnCount,
// 上下间距
mainAxisSpacing: mainSpacing,
// 左右间距
crossAxisSpacing: crossSpacing,
// 子项的宽高比
childAspectRatio: aspectRatio,
children: List.generate(list.length, (index) {
VideoItemEntity item = list[index];
return gridItem(item, index);
}),
);
}
gridItem(VideoItemEntity item, int index) {
return Stack(
alignment: Alignment.center,
children: [
if (item.thumbUrl.isNotEmpty)
Image.network(item.thumbUrl,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover),
if (item.thumbUrl.isEmpty)
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black.withOpacity(0.5),
),
Positioned(
bottom: 5,
child: Text(
item.title,
style: TextStyle(color: Colors.red),
)),
if (item.id == widget.lastViewId)
Container(
width: double.infinity,
height: double.infinity,
color: Colors.black.withOpacity(0.5),
alignment: Alignment.center,
child: Text("刚刚看过", style: TextStyle(color: Colors.white)),
),
],
);
}
// 模拟获取对应页码的数据
getPageData(int page) async {
if(page<0) return;
if (loadedPages.contains(page)) return;
loadedPages.add(page);
await Future.delayed(Duration(milliseconds: 300));
int startIndex = (page - 1) * pageSize;
int endIndex = startIndex + pageSize;
if (page == 1 && widget.lastViewId != 0) {
// 请求第一页,后端应该返回该id所在索引
setState(() {
lastViewIdIndex =
allList.indexWhere((element) => element.id == widget.lastViewId!);
});
}
return allList.sublist(startIndex, endIndex);
}
}
网友评论