Android Flutter自適應(yīng)瀑布流案例詳解
Flutter自適應(yīng)瀑布流
前言:在電商app經(jīng)常會(huì)看到首頁(yè)商品推薦的瀑布流,或者類似短視頻app首頁(yè)也是瀑布流,這些都是需要自適應(yīng)的,才能給用戶帶來好的體驗(yàn)
話不多說先上效果圖:
根據(jù)效果圖可以分為四步:
- 圖片自適應(yīng)
- 自適應(yīng)標(biāo)簽
- 上拉刷新和下拉加載
- 底部的點(diǎn)贊按鈕可以去掉或者自己修改樣式,我這里使用的like_button庫(kù)
注:本文使用的庫(kù):為啥這么多呢,因?yàn)槲野褕D片緩存這樣?xùn)|西都加上了,單純的瀑布流就用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.圖片自適應(yīng):
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.自適應(yīng)標(biāo)簽:
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.底部的點(diǎn)贊按鈕
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); }, )
這樣自適應(yīng)的瀑布流就完成了。
到此這篇關(guān)于Android Flutter自適應(yīng)瀑布流案例詳解的文章就介紹到這了,更多相關(guān)Android Flutter自適應(yīng)瀑布流內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決Android Studio一直停留在MyApplication:syncing的問題
這篇文章主要介紹了Android Studio一直停留在MyApplication:syncing的完美解決方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Android?RecyclerChart其它圖表繪制示例詳解
這篇文章主要為大家介紹了Android?RecyclerChart其它圖表繪制示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12android基于SwipeRefreshLayout實(shí)現(xiàn)類QQ的側(cè)滑刪除
本篇文章主要介紹了android基于SwipeRefreshLayout實(shí)現(xiàn)類QQ的側(cè)滑刪除,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-10-10Android初學(xué)者必須知道的10個(gè)技術(shù)
本篇內(nèi)容給大家整理10個(gè)作為Android初學(xué)者必須要了解和會(huì)用的技術(shù)以及詳細(xì)代碼分析,需要的朋友收藏下慢慢學(xué)習(xí)吧。2017-12-12Android ListView分頁(yè)功能實(shí)現(xiàn)方法
這篇文章主要為大家詳細(xì)介紹了Android ListView分頁(yè)功能的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05Android實(shí)現(xiàn)空心圓角矩形按鈕的實(shí)例代碼
頁(yè)面上有時(shí)會(huì)用到背景為空心圓角矩形的Button,可以通過xml繪制出來。這篇文章主要介紹了Android實(shí)現(xiàn)空心圓角矩形按鈕的實(shí)例代碼,需要的朋友參考下吧2017-01-01Android 相機(jī)相冊(cè)權(quán)限設(shè)置方法
今天小編就為大家分享一篇Android 相機(jī)相冊(cè)權(quán)限設(shè)置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08