這個功能是主要是學習flutter視訊的那個老師教給我的,感覺效果不錯,就來分享記錄一下,讓我下次使用起來方便一點,廢話少說,先看一下功能吧。
Demo使用的主要技術: 後端:json資料 前端: 1.TabBar+TabBarView實現Tab切換 2.使用flutter_staggered_grid_view外掛實現照片牆功能 3.使用Card元件讓你的介面更加美觀。
程式碼結構如下:
1.把json資料轉化為dart(model層)
travel_model.dart頁面
這個頁面主要是用來獲取照片牆顯示的資料,這裡我就不細說,因為我主要說照片牆的實現,而且程式碼比較多,如果你用工具的話,就畢竟簡單了,只需要把json資料貼上進去就可以轉化為dart了。json轉dart工具。
如果你想了解json資料是什麼內容,可以看這個地址。www.devio.org/io/flutter_…
///旅拍頁模型
class TravelItemModel {
int totalCount;
List<TravelItem> resultList;
TravelItemModel({this.totalCount, this.resultList});
TravelItemModel.fromJson(Map<String, dynamic> json) {
totalCount = json['totalCount'];
if (json['resultList'] != null) {
resultList = new List<TravelItem>();
json['resultList'].forEach((v) {
resultList.add(new TravelItem.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['totalCount'] = this.totalCount;
if (this.resultList != null) {
data['resultList'] = this.resultList.map((v) => v.toJson()).toList();
}
return data;
}
}
class TravelItem {
int type;
Article article;
TravelItem({this.type, this.article});
TravelItem.fromJson(Map<String, dynamic> json) {
type = json['type'];
article =
json['article'] != null ? new Article.fromJson(json['article']) : null;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['type'] = this.type;
if (this.article != null) {
data['article'] = this.article.toJson();
}
return data;
}
}
class Article {
int articleId;
String articleType;
int productType;
int sourceType;
String articleTitle;
Author author;
List<Images> images;
bool hasVideo;
int readCount;
int likeCount;
int commentCount;
List<Urls> urls;
List<Null> tags;
List<Topics> topics;
List<Pois> pois;
String publishTime;
String publishTimeDisplay;
String shootTime;
String shootTimeDisplay;
int level;
String distanceText;
bool isLike;
int imageCounts;
bool isCollected;
int collectCount;
int articleStatus;
String poiName;
Article(
{this.articleId,
this.articleType,
this.productType,
this.sourceType,
this.articleTitle,
this.author,
this.images,
this.hasVideo,
this.readCount,
this.likeCount,
this.commentCount,
this.urls,
this.tags,
this.topics,
this.pois,
this.publishTime,
this.publishTimeDisplay,
this.shootTime,
this.shootTimeDisplay,
this.level,
this.distanceText,
this.isLike,
this.imageCounts,
this.isCollected,
this.collectCount,
this.articleStatus,
this.poiName});
Article.fromJson(Map<String, dynamic> json) {
articleId = json['articleId'];
articleType = json['articleType'];
productType = json['productType'];
sourceType = json['sourceType'];
articleTitle = json['articleTitle'];
author =
json['author'] != null ? new Author.fromJson(json['author']) : null;
if (json['images'] != null) {
images = new List<Images>();
json['images'].forEach((v) {
images.add(new Images.fromJson(v));
});
}
hasVideo = json['hasVideo'];
readCount = json['readCount'];
likeCount = json['likeCount'];
commentCount = json['commentCount'];
if (json['urls'] != null) {
urls = new List<Urls>();
json['urls'].forEach((v) {
urls.add(new Urls.fromJson(v));
});
}
if (json['topics'] != null) {
topics = new List<Topics>();
json['topics'].forEach((v) {
topics.add(new Topics.fromJson(v));
});
}
if (json['pois'] != null) {
pois = new List<Pois>();
json['pois'].forEach((v) {
pois.add(new Pois.fromJson(v));
});
}
publishTime = json['publishTime'];
publishTimeDisplay = json['publishTimeDisplay'];
shootTime = json['shootTime'];
shootTimeDisplay = json['shootTimeDisplay'];
level = json['level'];
distanceText = json['distanceText'];
isLike = json['isLike'];
imageCounts = json['imageCounts'];
isCollected = json['isCollected'];
collectCount = json['collectCount'];
articleStatus = json['articleStatus'];
poiName = json['poiName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['articleId'] = this.articleId;
data['articleType'] = this.articleType;
data['productType'] = this.productType;
data['sourceType'] = this.sourceType;
data['articleTitle'] = this.articleTitle;
if (this.author != null ) {
data['author'] = this.author.toJson();
}
if (this.images != null) {
data['images'] = this.images.map((v) => v.toJson()).toList();
print("圖片"+data['images']);
}
data['hasVideo'] = this.hasVideo;
data['readCount'] = this.readCount;
data['likeCount'] = this.likeCount;
data['commentCount'] = this.commentCount;
if (this.urls != null) {
data['urls'] = this.urls.map((v) => v.toJson()).toList();
}
if (this.topics != null) {
data['topics'] = this.topics.map((v) => v.toJson()).toList();
}
if (this.pois != null) {
data['pois'] = this.pois.map((v) => v.toJson()).toList();
}
data['publishTime'] = this.publishTime;
data['publishTimeDisplay'] = this.publishTimeDisplay;
data['shootTime'] = this.shootTime;
data['shootTimeDisplay'] = this.shootTimeDisplay;
data['level'] = this.level;
data['distanceText'] = this.distanceText;
data['isLike'] = this.isLike;
data['imageCounts'] = this.imageCounts;
data['isCollected'] = this.isCollected;
data['collectCount'] = this.collectCount;
data['articleStatus'] = this.articleStatus;
data['poiName'] = this.poiName;
return data;
}
}
class Author {
int authorId;
String nickName;
String clientAuth;
String jumpUrl;
CoverImage coverImage;
int identityType;
String tag;
Author(
{this.authorId,
this.nickName,
this.clientAuth,
this.jumpUrl,
this.coverImage,
this.identityType,
this.tag});
Author.fromJson(Map<String, dynamic> json) {
authorId = json['authorId'];
nickName = json['nickName'];
clientAuth = json['clientAuth'];
jumpUrl = json['jumpUrl'];
coverImage = json['coverImage'] != null
? new CoverImage.fromJson(json['coverImage'])
: null;
identityType = json['identityType'];
tag = json['tag'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['authorId'] = this.authorId;
data['nickName'] = this.nickName;
data['clientAuth'] = this.clientAuth;
data['jumpUrl'] = this.jumpUrl;
if (this.coverImage != null) {
data['coverImage'] = this.coverImage.toJson();
}
data['identityType'] = this.identityType;
data['tag'] = this.tag;
return data;
}
}
class CoverImage {
String dynamicUrl;
String originalUrl;
CoverImage({this.dynamicUrl, this.originalUrl});
CoverImage.fromJson(Map<String, dynamic> json) {
dynamicUrl = json['dynamicUrl'];
originalUrl = json['originalUrl'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['dynamicUrl'] = this.dynamicUrl;
data['originalUrl'] = this.originalUrl;
return data;
}
}
class Images {
int imageId;
String dynamicUrl;
String originalUrl;
double width;
double height;
int mediaType;
bool isWaterMarked;
Images(
{this.imageId,
this.dynamicUrl,
this.originalUrl,
this.width,
this.height,
this.mediaType,
this.isWaterMarked});
Images.fromJson(Map<String, dynamic> json) {
imageId = json['imageId'];
dynamicUrl = json['dynamicUrl'];
originalUrl = json['originalUrl'];
width = json['width'];
height = json['height'];
mediaType = json['mediaType'];
isWaterMarked = json['isWaterMarked'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['imageId'] = this.imageId;
data['dynamicUrl'] = this.dynamicUrl;
data['originalUrl'] = this.originalUrl;
data['width'] = this.width;
data['height'] = this.height;
data['mediaType'] = this.mediaType;
data['isWaterMarked'] = this.isWaterMarked;
return data;
}
}
class Urls {
String version;
String appUrl;
String h5Url;
String wxUrl;
Urls({this.version, this.appUrl, this.h5Url, this.wxUrl});
Urls.fromJson(Map<String, dynamic> json) {
version = json['version'];
appUrl = json['appUrl'];
h5Url = json['h5Url'];
wxUrl = json['wxUrl'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['version'] = this.version;
data['appUrl'] = this.appUrl;
data['h5Url'] = this.h5Url;
data['wxUrl'] = this.wxUrl;
return data;
}
}
class Topics {
int topicId;
String topicName;
int level;
Topics({this.topicId, this.topicName, this.level});
Topics.fromJson(Map<String, dynamic> json) {
topicId = json['topicId'];
topicName = json['topicName'];
level = json['level'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['topicId'] = this.topicId;
data['topicName'] = this.topicName;
data['level'] = this.level;
return data;
}
}
class Pois {
int poiType;
int poiId;
String poiName;
int businessId;
int districtId;
PoiExt poiExt;
int source;
int isMain;
bool isInChina;
String countryName;
Pois(
{this.poiType,
this.poiId,
this.poiName,
this.businessId,
this.districtId,
this.poiExt,
this.source,
this.isMain,
this.isInChina,
this.countryName});
Pois.fromJson(Map<String, dynamic> json) {
poiType = json['poiType'];
poiId = json['poiId'];
poiName = json['poiName'];
businessId = json['businessId'];
districtId = json['districtId'];
poiExt =
json['poiExt'] != null ? new PoiExt.fromJson(json['poiExt']) : null;
source = json['source'];
isMain = json['isMain'];
isInChina = json['isInChina'];
countryName = json['countryName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['poiType'] = this.poiType;
data['poiId'] = this.poiId;
data['poiName'] = this.poiName;
data['businessId'] = this.businessId;
data['districtId'] = this.districtId;
if (this.poiExt != null) {
data['poiExt'] = this.poiExt.toJson();
}
data['source'] = this.source;
data['isMain'] = this.isMain;
data['isInChina'] = this.isInChina;
data['countryName'] = this.countryName;
return data;
}
}
class PoiExt {
String h5Url;
String appUrl;
PoiExt({this.h5Url, this.appUrl});
PoiExt.fromJson(Map<String, dynamic> json) {
h5Url = json['h5Url'];
appUrl = json['appUrl'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['h5Url'] = this.h5Url;
data['appUrl'] = this.appUrl;
return data;
}
}
複製程式碼
travel_tab_model.dart頁面
這個主要是用來獲取tabBar資料來顯示,例如:
///旅拍類別模型
class TravelTabModel {
Map params;
String url;
List<TravelTab> tabs;
TravelTabModel({this.url, this.tabs});
TravelTabModel.fromJson(Map<String, dynamic> json) {
url = json['url'];
params = json['params'];
if (json['tabs'] != null) {
tabs = new List<TravelTab>();
json['tabs'].forEach((v) {
tabs.add(new TravelTab.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['url'] = this.url;
if (this.tabs != null) {
data['tabs'] = this.tabs.map((v) => v.toJson()).toList();
}
return data;
}
}
class TravelTab {
String labelName;
String groupChannelCode;
TravelTab({this.labelName, this.groupChannelCode});
TravelTab.fromJson(Map<String, dynamic> json) {
labelName = json['labelName'];
groupChannelCode = json['groupChannelCode'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['labelName'] = this.labelName;
data['groupChannelCode'] = this.groupChannelCode;
return data;
}
}
複製程式碼
2.通過http請求獲取json資料(dao層)
travel_dao.dart頁面
首先我必須匯入http請求的依賴:
http: ^0.12.0+1
複製程式碼
其中 Utf8Decoder utf8decoder = Utf8Decoder(); 主要是用來避免中文亂碼問題的,其中下面的Params中的引數主要使用到防爬功能,因為資料是進行動態變化的
。
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:trip_demo/model/travel_model.dart';
///旅拍頁介面
var Params = {
"districtId": -1,
"groupChannelCode": "RX-OMF",
"type": null,
"lat": -180,
"lon": -180,
"locatedDistrictId": 0,
"pagePara": {
"pageIndex": 1,
"pageSize": 10,
"sortType": 9,
"sortDirection": 0
},
"imageCutType": 1,
"head": {'cid': "09031014111431397988"},
"contentType": "json"
};
class TravelDao {
static Future<TravelItemModel> fetch(
String url,Map params, String groupChannelCode, int pageIndex, int pageSize) async {
Map paramsMap = params['pagePara'];
paramsMap['pageIndex'] = pageIndex;
paramsMap['pageSize'] = pageSize;
params['groupChannelCode'] = groupChannelCode;
final response = await http.post(url, body: jsonEncode(params));
if (response.statusCode == 200) {
Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文亂碼
var result = json.decode(utf8decoder.convert(response.bodyBytes));
return TravelItemModel.fromJson(result);
} else {
throw Exception('Failed to load travel');
}
}
}
複製程式碼
travel_tab_dao頁面
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:trip_demo/model/travel_tab_model.dart';
///旅拍類別介面
class TravelTabDao {
static Future<TravelTabModel> fetch() async {
final response = await http
.get('http://www.devio.org/io/flutter_app/json/travel_page.json');
if (response.statusCode == 200) {
Utf8Decoder utf8decoder = Utf8Decoder(); // fix 中文亂碼
var result = json.decode(utf8decoder.convert(response.bodyBytes));
return TravelTabModel.fromJson(result);
} else {
throw Exception('Failed to load travel_page.json');
}
}
}
複製程式碼
3.實現demo介面的基礎功能(view層)
首先看一下main.dart介面的結構:
元件 | 功能 |
---|---|
TabBar | 實現tab主題 的切換 |
UnderlineIndicator | 主要實現tab切換時顯示在主題下方的進度條一樣的東西,讓滑動起來好看一點,因為不是flutter自帶的必須要匯入外掛underline_indicator: ^0.0.2 |
Flexible | 一般在row和column佈局內使用,功能和ExPanded類似,區別在於Flexible不會把空白區域自動填充 |
TabBarView | 用來存放切換的頁面 |
TravelTabPage | 這個我們自定義的元件,下面我們會詳細說明 |
import 'package:flutter/material.dart';
import 'package:underline_indicator/underline_indicator.dart';
import 'dao/travel_tab_dao.dart';
import 'model/travel_tab_model.dart';
import 'travel_tab_page.dart';
void main(){
runApp(MaterialApp(
home: TravelPage(),
));
}
class TravelPage extends StatefulWidget {
@override
_TravelPageState createState() => _TravelPageState();
}
class _TravelPageState extends State<TravelPage>
with TickerProviderStateMixin {
TabController _controller;
List<TravelTab> tabs = [];
TravelTabModel travelTabModel;
@override
void initState() {
_controller = TabController(length: 0, vsync: this);
TravelTabDao.fetch().then((TravelTabModel model) {
_controller = TabController(
length: model.tabs.length, vsync: this); //fix tab label 空白問題
setState(() {
tabs = model.tabs;
travelTabModel = model;
});
}).catchError((e) {
print(e);
});
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
color: Colors.white,
padding: EdgeInsets.only(top: 30),
child: TabBar(
controller: _controller,
isScrollable: true,
labelColor: Colors.black,
labelPadding: EdgeInsets.fromLTRB(20, 0, 10, 5),
indicator: UnderlineIndicator(
strokeCap: StrokeCap.round,
borderSide: BorderSide(
color: Color(0xff2fcfbb),
width: 3,
),
insets: EdgeInsets.only(bottom: 10)),
tabs: tabs.map<Tab>((TravelTab tab) {
return Tab(
text: tab.labelName,
);
}).toList()),
),
Flexible(
child: TabBarView(
controller: _controller,
children: tabs.map((TravelTab tab) {
return TravelTabPage(
travelUrl: travelTabModel.url,
params: travelTabModel.params,
groupChannelCode: tab.groupChannelCode,
);
}).toList()))
],
));
}
}
複製程式碼
travel_tab_page頁面
元件 | 功能 |
---|---|
LoadingContainer | 這個為我們自定義的方法,主要用於在資料未載入完,設定一個轉圈圈載入中的特效 |
RefreshIndicator | 下拉重新載入的元件 |
MediaQuery.removePadding | 移除元件直接的空白區域,讓UI看起來更加好看 |
StaggeredGridView.countBuilder | 用來實現照片牆的功能,需要匯入flutter_staggered_grid_view: ^0.3.4 外掛才能實現這個功能 |
_TravelItem | 自定義元件下面會細說 |
這些元件都畢竟基礎,我就不細說了,如果想去了解基礎元件的,可以去看我的下面兩篇文章: Flutter學習第四天:StatelessWidget常用元件總結,撐起Flutter的半邊天?
Flutter學習第五天:StatefulWidget常用元件總結,撐起Flutter的另外半邊天?
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:transparent_image/transparent_image.dart';
import 'dao/travel_dao.dart';
import 'method/loading_contain.dart';
import 'model/travel_model.dart';
const _TRAVEL_URL =
'https://m.ctrip.com/restapi/soa2/16189/json/searchTripShootListForHomePageV2?_fxpcqlniredt=09031014111431397988&__gw_appid=99999999&__gw_ver=1.0&__gw_from=10650013707&__gw_platform=H5';
const PAGE_SIZE = 10;
class TravelTabPage extends StatefulWidget {
final String travelUrl;
final Map params;
final String groupChannelCode;
const TravelTabPage(
{Key key, this.travelUrl, this.params, this.groupChannelCode})
: super(key: key);
@override
_TravelTabPageState createState() => _TravelTabPageState();
}
class _TravelTabPageState extends State<TravelTabPage>
with AutomaticKeepAliveClientMixin {
List<TravelItem> travelItems;
int pageIndex = 1;
bool _loading = true;
ScrollController _scrollController = ScrollController();
@override
void initState() {
_loadData();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_loadData(loadMore: true);
}
});
super.initState();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: LoadingContainer(
isLoading: _loading,
child: RefreshIndicator(
onRefresh: _handleRefresh,
child: MediaQuery.removePadding(
removeTop: true,
context: context,
child: StaggeredGridView.countBuilder(
controller: _scrollController,
crossAxisCount: 4,
itemCount: travelItems?.length ?? 0,
itemBuilder: (BuildContext context, int index) => _TravelItem(
index: index,
item: travelItems[index],
),
staggeredTileBuilder: (int index) => new StaggeredTile.fit(2),
)),
),
),
);
}
void _loadData({loadMore = false}) {
if (loadMore) {
pageIndex++;
} else {
pageIndex = 1;
}
TravelDao.fetch(widget.travelUrl ?? _TRAVEL_URL, widget.params,
widget.groupChannelCode, pageIndex, PAGE_SIZE)
.then((TravelItemModel model) {
_loading = false;
setState(() {
List<TravelItem> items = _filterItems(model.resultList);
if (travelItems != null) {
travelItems.addAll(items);
} else {
travelItems = items;
}
});
}).catchError((e) {
_loading = false;
print(e);
});
}
List<TravelItem> _filterItems(List<TravelItem> resultList) {
if (resultList == null) {
return [];
}
List<TravelItem> filterItems = [];
resultList.forEach((item) {
if (item.article != null) {
//移除article為空的模型
filterItems.add(item);
}
});
return filterItems;
}
@override
bool get wantKeepAlive => true;
Future<Null> _handleRefresh() async {
_loadData();
return null;
}
}
class _TravelItem extends StatelessWidget {
final TravelItem item;
final int index;
const _TravelItem({Key key, this.item, this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Card(
child: PhysicalModel(
color: Colors.transparent,
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_itemImage(context),
Container(
padding: EdgeInsets.all(4),
child: Text(
item.article.articleTitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 14, color: Colors.black87),
),
),
_infoText()
],
),
),
),
);
}
_itemImage(BuildContext context) {
final size = MediaQuery.of(context).size;
return Stack(
children: <Widget>[
Container(
//設定最小初始高度,防止動態圖片高度時的抖動
constraints: BoxConstraints(
minHeight: size.width / 2 - 10,
),
child: FadeInImage.memoryNetwork(
placeholder: kTransparentImage,
image: item.article.images[0]?.dynamicUrl??"https://youimg1.c-ctrip.com/target/1A0n12000000sei6395D2_R_800_10000_Q50.jpg",
fit: BoxFit.cover,
),
// child: Image.network(item.article.images[0]?.dynamicUrl??"https://youimg1.c-ctrip.com/target/1A0n12000000sei6395D2_R_800_10000_Q50.jpg"),
),
Positioned(
bottom: 8,
left: 8,
child: Container(
padding: EdgeInsets.fromLTRB(5, 1, 5, 1),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(10)),
child: Row(
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: 3),
child: Icon(
Icons.location_on,
color: Colors.white,
size: 12,
)),
LimitedBox(
maxWidth: 130,
child: Text(
_poiName(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: Colors.white, fontSize: 12),
),
)
],
),
))
],
);
}
String _poiName() {
return item.article.pois == null || item.article.pois.length == 0
? '未知'
: item.article.pois[0]?.poiName ?? '未知';
}
_infoText() {
return Container(
padding: EdgeInsets.fromLTRB(6, 0, 6, 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
PhysicalModel(
color: Colors.transparent,
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(12),
child: Image.network(
item.article.author?.coverImage?.dynamicUrl,
width: 24,
height: 24,
),
),
Container(
padding: EdgeInsets.all(5),
width: 90,
child: Text(
item.article.author?.nickName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 12),
),
)
],
),
Row(
children: <Widget>[
Icon(
Icons.thumb_up,
size: 14,
color: Colors.grey,
),
Padding(
padding: EdgeInsets.only(left: 3),
child: Text(
item.article.likeCount.toString(),
style: TextStyle(fontSize: 10),
),
)
],
)
],
),
);
}
}
複製程式碼
loading_contain.dart頁面
import 'package:flutter/material.dart';
class LoadingContainer extends StatelessWidget {
final Widget child;
final bool isLoading;
final bool cover;
const LoadingContainer(
{Key key,
@required this.isLoading,
this.cover = false,
@required this.child})
: super(key: key);
@override
Widget build(BuildContext context) {
return !cover
? !isLoading ? child : _loadingView
: Stack(
children: <Widget>[child, isLoading ? _loadingView : Container()],
);
}
Widget get _loadingView {
return Center(
child: CircularProgressIndicator(),
);
}
}
複製程式碼