Android Flutter自適應瀑布流案例詳解
Flutter自適應瀑布流
前言:在電商app經(jīng)常會看到首頁商品推薦的瀑布流,或者類似短視頻app首頁也是瀑布流,這些都是需要自適應的,才能給用戶帶來好的體驗
話不多說先上效果圖:


根據(jù)效果圖可以分為四步:
- 圖片自適應
- 自適應標簽
- 上拉刷新和下拉加載
- 底部的點贊按鈕可以去掉或者自己修改樣式,我這里使用的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);
},
)
這樣自適應的瀑布流就完成了。
到此這篇關于Android Flutter自適應瀑布流案例詳解的文章就介紹到這了,更多相關Android Flutter自適應瀑布流內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決Android Studio一直停留在MyApplication:syncing的問題
這篇文章主要介紹了Android Studio一直停留在MyApplication:syncing的完美解決方案,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-10-10
Android?RecyclerChart其它圖表繪制示例詳解
這篇文章主要為大家介紹了Android?RecyclerChart其它圖表繪制示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
android基于SwipeRefreshLayout實現(xiàn)類QQ的側滑刪除
本篇文章主要介紹了android基于SwipeRefreshLayout實現(xiàn)類QQ的側滑刪除,非常具有實用價值,需要的朋友可以參考下2017-10-10
Android ListView分頁功能實現(xiàn)方法
這篇文章主要為大家詳細介紹了Android ListView分頁功能的實現(xiàn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-05-05

