flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果
在之前处理类似微博帖子列表及下拉刷新上拉加载效果,刷新使用的是EasyRefresh
一、引入EasyRefresh与likeButton
在工程的pubspec.yaml中引入插件
# 下拉刷新、上拉更多
easy_refresh: ^3.3.2+1
pull_to_refresh: ^2.0.0
需要使用EasyRefreshController来控制处理刷新,初始化
@override
void initState() {
super.initState();
_controller = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
需要在onRefresh与onLoad来处理下拉刷新上拉加载数据。
二、类似微博帖子列表及下拉刷新上拉加载效果
类似微博帖子列表,这里定义帖子item,每个帖子中可能包括多张图片。
NoteItem:
class NoteItem {
String? feedId;
String? coverImageUrl;
String? title;
String? textContent;
String? username;
String? avatarUrl;
String? time;
String? likeNum;
bool? liked;
String? categoryName;
List<NoteImage>? images;
}
class NoteImage {
String? imageUrl;
String? imageWidth;
String? imageHeight;
}
定义每个帖子的Widget:NoteListItemWidget,NoteListItemWidget结构是用户头像,描述文本,帖子图片,点赞、评论、分享栏。
NoteListItemWidget:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_app_demolab/display/note_item.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:like_button/like_button.dart';
class NoteListItemWidget extends StatefulWidget {
NoteListItemWidget({
super.key,
required this.noteItem,
});
final NoteItem noteItem;
@override
_NoteListItemWidgetState createState() => _NoteListItemWidgetState();
}
class _NoteListItemWidgetState extends State<NoteListItemWidget> {
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(vertical: 5.0, horizontal: 0.0),
padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 15.0),
//边框设置
decoration: new BoxDecoration(
//背景
color: Color(0xFFFFFFFF),
//设置四周圆角 角度 这里的角度应该为 父Container height 的一半
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
//设置四周边框
border: new Border.all(width: 1, color: Color(0xFFf1f1f1)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
NoteListAuthorBar(
username: widget.noteItem.username,
avatarUrl: widget.noteItem.avatarUrl,
showTime: widget.noteItem.time
),
Text(
widget.noteItem.textContent??"",
maxLines: 5,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Color(0xFF666666),
),
),
NoteListImage(images: (widget.noteItem.images != null
? widget.noteItem.images
: [])),
NoteListBottom(
liked: widget.noteItem.liked,
likeNum: widget.noteItem.likeNum,
categoryName: widget.noteItem.categoryName
),
],
),
);
}
}
// 顶部header
class NoteListAuthorBar extends StatelessWidget {
const NoteListAuthorBar({
super.key,
this.username,
this.avatarUrl,
this.showTime,
});
final String? username;
final String? avatarUrl;
final String? showTime;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(bottom: 10),
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircleAvatar(
backgroundImage: NetworkImage(avatarUrl ?? ""),
),
Padding(
padding: EdgeInsets.only(left: 10),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
username ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Color(0xFF444444),
),
),
Padding(
padding: EdgeInsets.only(top: 5),
),
Text(
showTime ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Color(0xFF444444),
),
),
],
)
],
),
);
}
}
// 显示图片
class NoteListImage extends StatelessWidget {
const NoteListImage({
super.key,
this.images,
});
final List<NoteImage>? images;
Widget layoutOfImages(List images, BuildContext context) {
double screenWidth = MediaQuery
.of(context)
.size
.width;
double containW = screenWidth - 30;
if (images.length == 1) {
NoteImage noteImage = images[0];
double imgWidth = 0;
double imgHeight = 0;
if (noteImage.imageWidth != null &&
noteImage.imageWidth!.isNotEmpty &&
noteImage.imageHeight != null &&
noteImage.imageHeight!.isNotEmpty) {
imgWidth = double.parse(noteImage.imageWidth!);
imgHeight = double.parse(noteImage.imageHeight!);
if (imgWidth > 0 && imgHeight > 0) {
if (imgWidth < 1.0 && imgHeight < 1.0) {
double maxLen = containW / 2.0;
double showW = maxLen;
double showH = maxLen;
return Container(
height: showH,
width: showW,
child: CachedNetworkImage(
imageUrl: noteImage.imageUrl ?? "",
placeholder: (context, url) =>
Center(child: CupertinoActivityIndicator()),
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
),
);
} else {
double imageScale = imgWidth / imgHeight;
double len1_3 = (containW - 20) / 3.0;
double maxLen = len1_3 * 2 + 10;
double showW = 0;
double showH = 0;
if (imageScale > 1.0) {
// 横图
showW = maxLen;
showH = showW / imageScale;
} else {
// 竖图
showH = maxLen;
showW = imageScale * showH;
}
return Container(
height: showH,
width: showW,
child: CachedNetworkImage(
imageUrl: noteImage.imageUrl??"",
placeholder: (context, url) =>
Center(child: CupertinoActivityIndicator()),
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
),
);
}
}
}
} else if (images.length >= 2) {
double showImageW = 0;
if (images.length == 2 || images.length == 4) {
showImageW = (containW - 10) / 2.0;
} else {
showImageW = (containW - 20) / 3.0;
}
List<Widget> imageWidgets = [];
for (int index = 0; index < images.length; index++) {
NoteImage noteImage = images[index];
Widget widget = Container(
height: showImageW,
width: showImageW,
child: CachedNetworkImage(
imageUrl: noteImage.imageUrl??"",
placeholder: (context, url) =>
Center(child: CupertinoActivityIndicator()),
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
)
);
imageWidgets.add(widget);
}
return Container(
alignment: Alignment.center,
width: containW,
child: Wrap(
spacing: 8.0,
// 主轴(水平)方向间距
runSpacing: 8.0,
// 纵轴(垂直)方向间距
alignment: WrapAlignment.start,
//沿主轴方向居中
crossAxisAlignment: WrapCrossAlignment.center,
children: imageWidgets,
),
);
}
return Container();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
child: layoutOfImages(this.images??[], context),
);
}
}
// 显示底部
class NoteListBottom extends StatelessWidget {
const NoteListBottom({
super.key,
this.liked,
this.likeNum,
this.categoryName,
});
final bool? liked;
final String? likeNum;
final String? categoryName;
@override
Widget build(BuildContext context) {
return Container(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
//边框设置
decoration: new BoxDecoration(
//背景
color: Color(0xFFFFFFFF),
//设置四周圆角 角度 这里的角度应该为 父Container height 的一半
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10.0),
topRight: Radius.circular(10.0)),
//设置四周边框
border: new Border.all(width: 1, color: Color(0xFFf1f1f1)),
),
child: Text(
this.categoryName??"",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
color: Color(0xFF444444),
),
),
),
Container(
padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LikeButton(
likeBuilder: (bool isLiked) {
return Icon(Icons.thumb_up,
color: (isLiked ? Colors.deepOrange : Color(
0xFFA9A9A9)));
},
likeCount: int.parse(this.likeNum??"0"),
isLiked: this.liked,
),
TextButton(
child: Wrap(
// 可以通过设置两个基础组件的间距
alignment: WrapAlignment.center, //沿主轴方向居中
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 5,
children: [
Icon(Icons.comment, color: Color(0xFFA9A9A9)),
Text("评论",
style:
TextStyle(fontSize: 12, color: Color(0xFF888888)))
],
),
onPressed: () {},
),
TextButton(
child: Wrap(
// 可以通过设置两个基础组件的间距
alignment: WrapAlignment.center, //沿主轴方向居中
crossAxisAlignment: WrapCrossAlignment.center,
spacing: 5,
children: [
Icon(Icons.share, color: Color(0xFFA9A9A9)),
Text("分享",
style:
TextStyle(fontSize: 12, color: Color(0xFF888888)))
],
),
onPressed: () {},
),
],
),
)
],
),
);
}
}
最后,我们在page中处理类似帖子列表DisplayPage
DisplayPage:
import 'dart:math';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_demolab/display/note_item.dart';
import 'package:flutter_app_demolab/display/note_item_widget.dart';
class DisplayPage extends StatefulWidget {
const DisplayPage({super.key});
@override
State<DisplayPage> createState() => _DisplayPageState();
}
class _DisplayPageState extends State<DisplayPage> {
int _count = 10;
late EasyRefreshController _controller;
bool isLoading = false;
ScrollController scrollController = ScrollController();
List<NoteItem> list = [];
List<NoteImage> noteImages = [];
String randomBit() {
String scopeF = '0123456789'; //首位
String result = '';
result = scopeF[Random().nextInt(scopeF.length)];
return result;
}
@override
void initState() {
initItems();
super.initState();
_controller = EasyRefreshController(
controlFinishRefresh: true,
controlFinishLoad: true,
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void initItems() {
for (int index = 0; index < 12; index++) {
NoteImage model = NoteImage();
noteImages.add(model);
if (index == 0) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202010/11/20201011085349_R2CjU.thumb.1000_0.jpeg";
model.imageWidth = "580";
model.imageHeight = "880";
} else if (index == 1) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202011/15/20201115080806_7ddba.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "580";
} else if (index == 2) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202011/15/20201115100436_7ffc6.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "560";
} else if (index == 3) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/item/202006/09/20200609232715_yvqkd.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "380";
} else if (index == 4) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144923_425bc.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "430";
} else if (index == 5) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202010/11/20201011085351_3d2cS.thumb.1000_0.jpeg";
model.imageWidth = "580";
model.imageHeight = "850";
} else if (index == 6) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202009/28/20200928184639_sxrum.thumb.1000_0.jpeg";
model.imageWidth = "580";
model.imageHeight = "810";
} else if (index == 7) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202012/03/20201203145208_55d8c.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "430";
} else if (index == 8) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/item/202006/09/20200609232715_pjsin.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "860";
} else if (index == 9) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144929_cd406.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "450";
} else if (index == 10) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144705_40405.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "450";
} else if (index == 11) {
model.imageUrl =
"https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144708_d5287.thumb.1000_0.jpg";
model.imageWidth = "580";
model.imageHeight = "750";
}
}
for (int index = 0; index < 20; index++) {
NoteItem model = NoteItem();
model.liked = false;
list.add(model);
if (index % 5 == 0) {
model.username = "可可";
model.avatarUrl =
"https://c-ssl.duitang.com/uploads/item/202007/22/20200722212206_ifscm.thumb.1000_0.jpg";
model.time = "08/01 13:51";
model.textContent = "等轮到你讲话时再说,认真聆听对方的谈话,千万别打断对方讲话,耐心等着,轮到你时再讲。";
model.likeNum = "861380";
model.categoryName = "兽圈";
} else if (index % 5 == 2) {
model.username = "琪琪";
model.avatarUrl =
"https://c-ssl.duitang.com/uploads/item/202007/22/20200722212209_insvm.thumb.1000_0.png";
model.time = "08/01 13:51";
model.textContent = "记得有活动,在活动现场等你哦~";
model.likeNum = "50999";
model.categoryName = "娃圈";
} else if (index % 5 == 4) {
model.username = "悦悦";
model.avatarUrl =
"https://c-ssl.duitang.com/uploads/item/202007/19/20200719102754_jrcoe.thumb.1000_0.jpg";
model.time = "08/01 13:51";
model.textContent = "说一说你想要的形象模型,晒一晒你的喜欢";
model.likeNum = "6150";
model.categoryName = "Pia戏圈";
} else {
model.username = "爱丽多啦";
model.avatarUrl =
"https://c-ssl.duitang.com/uploads/item/202007/19/20200719102756_ihfku.thumb.1000_0.jpg";
model.time = "08/01 13:51";
model.textContent = "你永远不懂我喜欢的东西,555";
model.likeNum = "1507";
model.categoryName = "语c圈";
}
List<NoteImage> tmpImages = [];
String randomf = randomBit();
String randomt = randomBit();
int f = int.parse(randomf);
int t = int.parse(randomt);
print("f:$f");
print("t:$t");
if (f < t) {
tmpImages.addAll(noteImages.sublist(f, t));
} else if (f > t) {
tmpImages.addAll(noteImages.sublist(t, f));
} else {
tmpImages.addAll(noteImages.sublist(t, t+1));
}
model.images = tmpImages;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('EasyRefresh'),
),
body: EasyRefresh(
controller: _controller,
header: const ClassicHeader(),
footer: const ClassicFooter(),
onRefresh: () async {
await Future.delayed(const Duration(seconds: 4));
if (!mounted) {
return;
}
list = [];
initItems();
setState(() {
_count = 10;
});
_controller.finishRefresh();
_controller.resetFooter();
},
onLoad: () async {
await Future.delayed(const Duration(seconds: 4));
if (!mounted) {
return;
}
initItems();
setState(() {
_count += 5;
});
_controller.finishLoad(
_count >= 200 ? IndicatorResult.noMore : IndicatorResult.success);
},
child: ListView.builder(
itemBuilder: (context, index) {
return NoteListItemWidget(noteItem: this.list[index]);
},
itemCount: this.list.length,
),
),
);
}
}
效果图如下:
三、小结
flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果
学习记录,每天不停进步。