Android Flutter自适应瀑布流案例详解
作者:阿 T 时间:2023-05-31 17:44:21
Flutter自适应瀑布流
前言:在电商app经常会看到首页商品推荐的瀑布流,或者类似短视频app首页也是瀑布流,这些都是需要自适应的,才能给用户带来好的体验
话不多说先上效果图:
根据效果图可以分为四步:
图片自适应
自适应标签
上拉刷新和下拉加载
底部的点赞按钮可以去掉或者自己修改样式,我这里使用的like_button库
注:本文使用的库:为啥这么多呢,因为我把图片缓存这样东西都加上了,单纯的瀑布流就用waterfall_flow
waterfall_flow: ^3.0.1
extended_image: any
extended_sliver: any
ff_annotation_route_library: any
http_client_helper: any
intl: any
like_button: any
loading_more_list: any
pull_to_refresh_notification: any
url_launcher: any
1.图片自适应:
Widget image = Stack(
children: <Widget>[
ExtendedImage.network(
item.imageUrl,
shape: BoxShape.rectangle,
//clearMemoryCacheWhenDispose: true,
border: Border.all(color: Colors.grey.withOpacity(0.4), width: 1.0),
borderRadius: const BorderRadius.all(
Radius.circular(10.0),
),
loadStateChanged: (ExtendedImageState value) {
if (value.extendedImageLoadState == LoadState.loading) {
Widget loadingWidget = Container(
alignment: Alignment.center,
color: Colors.grey.withOpacity(0.8),
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor:
AlwaysStoppedAnimation<Color>(Theme.of(c).primaryColor),
),
);
if (!konwSized) {
//todo: not work in web
loadingWidget = AspectRatio(
aspectRatio: 1.0,
child: loadingWidget,
);
}
return loadingWidget;
} else if (value.extendedImageLoadState == LoadState.completed) {
item.imageRawSize = Size(
value.extendedImageInfo.image.width.toDouble(),
value.extendedImageInfo.image.height.toDouble());
}
return null;
},
),
Positioned(
top: 5.0,
right: 5.0,
child: Container(
padding: const EdgeInsets.all(3.0),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.6),
border: Border.all(color: Colors.grey.withOpacity(0.4), width: 1.0),
borderRadius: const BorderRadius.all(
Radius.circular(5.0),
),
),
child: Text(
'${index + 1}',
textAlign: TextAlign.center,
style: const TextStyle(fontSize: fontSize, color: Colors.white),
),
),
)
],
);
if (konwSized) {
image = AspectRatio(
aspectRatio: item.imageSize.width / item.imageSize.height,
child: image,
);
} else if (item.imageRawSize != null) {
image = AspectRatio(
aspectRatio: item.imageRawSize.width / item.imageRawSize.height,
child: image,
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
image,
const SizedBox(
height: 5.0,
),
buildTagsWidget(item),
const SizedBox(
height: 5.0,
),
buildBottomWidget(item),
],
);
}
2.自适应标签:
Widget buildTagsWidget(
TuChongItem item, {
int maxNum = 6,
}) {
const double fontSize = 12.0;
return Wrap(
runSpacing: 5.0,
spacing: 5.0,
children: item.tags.take(maxNum).map<Widget>((String tag) {
final Color color = item.tagColors[item.tags.indexOf(tag)];
return Container(
padding: const EdgeInsets.all(3.0),
decoration: BoxDecoration(
color: color,
border: Border.all(color: Colors.grey.withOpacity(0.4), width: 1.0),
borderRadius: const BorderRadius.all(
Radius.circular(5.0),
),
),
child: Text(
tag,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: fontSize,
color: color.computeLuminance() < 0.5
? Colors.white
: Colors.black),
),
);
}).toList());
}
3.上拉刷新和下拉加载
class PullToRefreshHeader extends StatelessWidget {
const PullToRefreshHeader(this.info, this.lastRefreshTime, {this.color});
final PullToRefreshScrollNotificationInfo info;
final DateTime lastRefreshTime;
final Color color;
@override
Widget build(BuildContext context) {
if (info == null) {
return Container();
}
String text = '';
if (info.mode == RefreshIndicatorMode.armed) {
text = 'Release to refresh';
} else if (info.mode == RefreshIndicatorMode.refresh ||
info.mode == RefreshIndicatorMode.snap) {
text = 'Loading...';
} else if (info.mode == RefreshIndicatorMode.done) {
text = 'Refresh completed.';
} else if (info.mode == RefreshIndicatorMode.drag) {
text = 'Pull to refresh';
} else if (info.mode == RefreshIndicatorMode.canceled) {
text = 'Cancel refresh';
}
final TextStyle ts = const TextStyle(
color: Colors.grey,
).copyWith(fontSize: 13);
final double dragOffset = info?.dragOffset ?? 0.0;
final DateTime time = lastRefreshTime ?? DateTime.now();
final double top = -hideHeight + dragOffset;
return Container(
height: dragOffset,
color: color ?? Colors.transparent,
//padding: EdgeInsets.only(top: dragOffset / 3),
//padding: EdgeInsets.only(bottom: 5.0),
child: Stack(
children: <Widget>[
Positioned(
left: 0.0,
right: 0.0,
top: top,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Container(
alignment: Alignment.centerRight,
child: RefreshImage(top),
margin: const EdgeInsets.only(right: 12.0),
),
),
Column(
children: <Widget>[
Text(
text,
style: ts,
),
Text(
'Last updated:' +
DateFormat('yyyy-MM-dd hh:mm').format(time),
style: ts.copyWith(fontSize: 12),
)
],
),
Expanded(
child: Container(),
),
],
),
)
],
),
);
}
}
class RefreshImage extends StatelessWidget {
const RefreshImage(this.top);
final double top;
@override
Widget build(BuildContext context) {
const double imageSize = 40;
return ExtendedImage.asset(
Assets.assets_fluttercandies_grey_png,
width: imageSize,
height: imageSize,
afterPaintImage: (Canvas canvas, Rect rect, ui.Image image, Paint paint) {
final double imageHeight = image.height.toDouble();
final double imageWidth = image.width.toDouble();
final Size size = rect.size;
final double y = (1 - min(top / (refreshHeight - hideHeight), 1)) *
imageHeight;
canvas.drawImageRect(
image,
Rect.fromLTWH(0.0, y, imageWidth, imageHeight - y),
Rect.fromLTWH(rect.left, rect.top + y / imageHeight * size.height,
size.width, (imageHeight - y) / imageHeight * size.height),
Paint()
..colorFilter =
const ColorFilter.mode(Color(0xFFea5504), BlendMode.srcIn)
..isAntiAlias = false
..filterQuality = FilterQuality.low);
//canvas.restore();
},
);
}
}
4.底部的点赞按钮
LikeButton(
size: 18.0,
isLiked: item.isFavorite,
likeCount: item.favorites,
countBuilder: (int count, bool isLiked, String text) {
final ColorSwatch<int> color =
isLiked ? Colors.pinkAccent : Colors.grey;
Widget result;
if (count == 0) {
result = Text(
'love',
style: TextStyle(color: color, fontSize: fontSize),
);
} else {
result = Text(
count >= 1000 ? (count / 1000.0).toStringAsFixed(1) + 'k' : text,
style: TextStyle(color: color, fontSize: fontSize),
);
}
return result;
},
likeCountAnimationType: item.favorites < 1000
? LikeCountAnimationType.part
: LikeCountAnimationType.none,
onTap: (bool isLiked) {
return onLikeButtonTap(isLiked, item);
},
)
这样自适应的瀑布流就完成了。
来源:https://blog.csdn.net/txaz6/article/details/120248042
标签:Android,Flutter
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
可伸缩的textview详解(推荐)
2021-11-06 00:34:51
![](https://img.aspxhome.com/file/2023/5/111675_0s.png)
Android Rreact Native 常见错误总结
2021-07-11 16:39:59
使用Java Minio搭建自己的文件系统详解
2023-05-20 05:15:01
![](https://img.aspxhome.com/file/2023/7/130907_0s.jpg)
浅谈SpringMVC的执行流程
2023-09-30 17:59:59
![](https://img.aspxhome.com/file/2023/8/62358_0s.png)
springboot前后台数据交互的示例代码
2023-11-26 21:15:07
![](https://img.aspxhome.com/file/2023/8/89468_0s.png)
java多线程-同步块实例讲解
2022-06-21 02:10:41
Android中读取中文字符的文件与文件读取相关介绍
2022-02-02 07:20:53
使用 Spring Boot 2.0 + WebFlux 实现 RESTful API功能
2023-12-22 19:51:03
![](https://img.aspxhome.com/file/2023/1/96521_0s.png)
Java 实现二叉搜索树的查找、插入、删除、遍历
2023-10-04 05:37:39
![](https://img.aspxhome.com/file/2023/5/83025_0s.png)
spring cloud升级到spring boot 2.x/Finchley.RELEASE遇到的坑
2022-01-04 20:40:56
![](https://img.aspxhome.com/file/2023/6/90026_0s.jpg)
Java中对话框的弹出方法
2022-04-24 14:35:52
Spring boot搭建邮件服务的完整步骤
2023-07-20 06:58:34
![](https://img.aspxhome.com/file/2023/2/122182_0s.jpg)
如何使用java修改文件所有者及其权限
2023-11-16 09:35:53
![](https://img.aspxhome.com/file/2023/9/76309_0s.png)
C#实现图书管理系统
2023-03-24 04:30:35
![](https://img.aspxhome.com/file/2023/9/106469_0s.jpg)
Spring Boot整合mybatis并自动生成mapper和实体实例解析
2022-01-28 20:00:02
C#导入导出EXCEL文件的代码实例
2022-04-21 07:15:15
C# 崩溃异常中研究页堆布局的详细过程
2022-11-06 17:04:08
![](https://img.aspxhome.com/file/2023/9/129649_0s.png)
Opencv实现读取摄像头和视频数据
2023-07-16 15:19:58
关于Java中增强for循环使用的注意事项
2021-08-09 16:47:43
![](https://img.aspxhome.com/file/2023/7/63307_0s.png)
推荐史上最全的IDEA好用插件
2023-02-22 21:35:04
![](https://img.aspxhome.com/file/2023/8/62378_0s.png)