上期實(shí)現(xiàn)了一個(gè)網(wǎng)絡(luò)輪播圖的效果,自定義了一個(gè)輪播圖組件,繼承自StatefulWidget,我們知道Flutter中并沒有像Android中activity的概念。頁面見的跳轉(zhuǎn)是通過路由從一個(gè)全屏組件跳轉(zhuǎn)到另外的一個(gè)全屏組件,那如果我想在A組件中更新B組件的數(shù)據(jù)應(yīng)該怎么實(shí)現(xiàn)呢?
今天我們來實(shí)現(xiàn)一個(gè)支持篩選的列表頁面。前面我們已經(jīng)實(shí)現(xiàn)來一個(gè)支持下拉刷新和上拉加載更多的列表組件,這里就不在做更多介紹來,效果圖如下:
通過點(diǎn)擊左滑菜單篩選列表的數(shù)據(jù)。由于列表在之前的一篇文章已經(jīng)說明過Flutter學(xué)習(xí)四之實(shí)現(xiàn)一個(gè)支持刷新加載的列表,所以這里就直接上列表的代碼:我們將列表定義程一個(gè)組件,方便,頁面引用,列表的請求也放在組件內(nèi)部。
class ArticleListBaseWidget extends StatefulWidget {
int cid = 0; //文章分類的id
@override
State<StatefulWidget> createState() {
return ArticleListBaseState();
}
ArticleListBaseWidget({this.cid});
}
class ArticleListBaseState extends State<ArticleListBaseWidget> {
RefreshController refreshController =
RefreshController(initialRefresh: true);
int pageNum = 0;
List<TopArticleBeanData> _articles = [];
void onRefresh() {
pageNum = 0;
getArticle(true, getApiName());
}
void loadMore() {
pageNum++;
getArticle(false, getApiName());
}
//根據(jù)cid參數(shù)來處理apiName,如果沒有傳cid參數(shù)進(jìn)來表示不支持分類篩選。
String getApiName() {
String apiName = "";
if (widget.cid > 0) {
apiName = "article/list/$pageNum/json?cid=${widget.cid}";
} else {
apiName = "article/list/$pageNum/json";
}
print("apiName=$apiName");
return apiName;
}
///置頂文章
void getArticle(bool isRefresh, String apiName) async {
///文章接口請求
dio.get(apiName).then((value) {
///文章實(shí)體解析
ArticleListEntityEntity articleBeanEntity =
ArticleListEntityEntity().fromJson(jsonDecode(value.toString()));
if (isRefresh) {
_articles = articleBeanEntity.data.datas;
} else {
_articles.addAll(articleBeanEntity.data.datas);
}
///接口調(diào)用成功后更新數(shù)據(jù)需要調(diào)用setState()方法
setState(() {
});
if (articleBeanEntity.data.datas.length ==0) {
//如果接口沒有數(shù)據(jù)返回
if (isRefresh) {
//如果是下拉刷新的時(shí)候并且接口沒有返回?cái)?shù)據(jù),隱藏列表控件,展示空頁面
refreshController.refreshFailed();
} else {
//如果是上啦加載更多并且沒有返回?cái)?shù)據(jù),就展示列表控件,但是下拉提示沒有更多數(shù)據(jù)了
refreshController.loadNoData();
}
} else {
//如果有數(shù)據(jù)返回,展示列表控件
if (isRefresh) {
//如果是下拉刷新,并且有數(shù)據(jù)返回正常展示頁面數(shù)據(jù)
refreshController.refreshCompleted();
} else {
//如果是上啦加載,并且有數(shù)據(jù)返回正常展示頁面數(shù)據(jù)
refreshController.loadComplete();
}
}
}).catchError((onError) {
if (isRefresh) {
//如果下拉刷新,并且接口出錯(cuò),展示錯(cuò)誤頁面
refreshController.refreshFailed();
} else {
//如果上拉加載更多,并且接口出錯(cuò),展示列表控件,底部下拉位置展示加載失敗
refreshController.loadFailed();
}
});
}
@override
Widget build(BuildContext context) {
return
SmartRefresher(
controller: refreshController,
enablePullUp: true,
onRefresh: onRefresh,
onLoading: loadMore,
header: WaterDropHeader(),
footer: ClassicFooter(),
child:
ListView.builder(
itemBuilder: (context, index) => ItemPage(_articles[index]),
itemCount: _articles.length)
);
}
}
因?yàn)橐獙?shí)現(xiàn)的列表支持篩選項(xiàng),所以我們這里要在構(gòu)造方法中接收一個(gè)cid參數(shù),用來進(jìn)行列表篩選的,代碼很簡單,主要是定義來一個(gè)下拉刷新和上拉加載更多要執(zhí)行的方法,然后在接口返回中針對返回的數(shù)據(jù)處理來對應(yīng)的刷新狀態(tài),以及對異常的處理,注釋已經(jīng)寫的很清楚來。
接下來我們新建一個(gè)頁面,
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("文章列表")),
endDrawer: drawerSystem(),
body:ArticleListBaseWidget(key:key,cid: cid));
}
這里endDrawer方法是用來添加一個(gè)左滑菜單欄的,這里我們用來顯示需要篩選的分類數(shù)據(jù)組件drawerSystem,該組件是由兩個(gè)聯(lián)動(dòng)的列表組成的,同時(shí)在構(gòu)造方法中,傳人兩個(gè)列表的點(diǎn)擊時(shí)間,方便調(diào)用該組件的頁面進(jìn)行數(shù)據(jù)更新。這里要注意的是獲取狀態(tài)欄的高度和appbar的高度,還有我們要在endDrawer中自定義一個(gè)標(biāo)題欄,但是endDrawer頁面默認(rèn)會(huì)有一個(gè)距離頂部的空白高度,我們要利用MediaQuery.removePadding方法來去掉頂部留白的部分,然后設(shè)置自己定義的標(biāo)題欄。這里的appbar高度用常量kToolbarHeight來獲取,MediaQuery.of(context).padding.top就是狀態(tài)欄的高度,看到往上很多是直接寫死,這樣在不同的機(jī)型上面展示的效果會(huì)不一樣的。
具體代碼:
Widget drawerSystem() {
return MediaQuery.removePadding(
context: context,
removeTop: true,
child: Container(
color: Colors.blue,
width: 320,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Container(
child: Text("體系數(shù)據(jù)"),
height: kToolbarHeight + MediaQuery.of(context).padding.top,
//appbar高度+狀態(tài)欄高度
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top),
alignment: Alignment.center,
color: Colors.grey[200]),
Expanded(
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Container(
child: ListView.separated(
separatorBuilder: (context, index) {
return Divider(
height: 1, color: Colors.grey[50]);
},
itemBuilder: (context, index) => ItemPageSystem(
screenList[index],
null,
index,
(index) => {onItemClick(index)}),
itemCount: screenList.length),
color: Colors.white)),
Expanded(
child: Container(
child: ListView.separated(
separatorBuilder: (context, index) {
return Divider(
height: 1, color: Colors.white);
},
itemBuilder: (context, index) => ItemPageSystem(
null,
screenChildList[index],
index,
(index) => onItemClickChild(index)),
itemCount: screenChildList.length),
color: Colors.grey[50]))
],
))
],
)));
}
ItemPageSystem是listView每該item的樣式,可以自己定義,這里要注意的一下,就是listview item的點(diǎn)擊事件,這里為來方便在父組件進(jìn)行數(shù)據(jù)的處理,所以是從item組件的構(gòu)造方法將方法傳進(jìn)去的,
ItemPageSystem(
this.screenDataBean, this.screenChild, this.index, this.function);
然后就可以在點(diǎn)擊事件處理篩選的聯(lián)動(dòng)效果了,
//一級篩選點(diǎn)擊事件處理
onItemClick(int index) {
setState(() {
//全局記住點(diǎn)擊位置
this.index = index;
//設(shè)置二級菜單數(shù)據(jù)集合
screenChildList = screenList[index].children;
//遍歷一級數(shù)據(jù)設(shè)置一級菜單標(biāo)示,是否選中
updateListSelect(index, screenList);
});
}
//二級篩選事件處理
onItemClickChild(int index) {
setState(() {
//全局記住二級菜單點(diǎn)擊位置
indexChild = index;
//雙層循環(huán)遍歷清空二級菜單為非選中狀態(tài)
for (int i = 0; i < screenList.length; i++) {
updateListSelect(-1, screenList[i].children);
}
//設(shè)置當(dāng)前點(diǎn)擊數(shù)據(jù)為選中狀態(tài)
updateListSelect(index, screenChildList);
cid = screenChildList[index].id;
Navigator.pop(context);//關(guān)閉側(cè)邊菜單欄
key.currentState.refreshController.requestRefresh();
});
}
到這一步基本已經(jīng)完成了,我們看下效果發(fā)現(xiàn),點(diǎn)擊了篩選后并不能更新列表數(shù)據(jù),這是為什么呢,查找了半天不知道問題處在哪里,通過查閱文檔發(fā)現(xiàn),如果想在父組件里面更新繼承自StatefulWidget的組件的數(shù)據(jù),光設(shè)置setStat還是不行,因?yàn)榻M件的Key是相同的沒有改變,所以要想在父組件更新StatefulWidget組件的數(shù)據(jù),需要用到GlobalKey,GlobalKey 能夠跨 Widget 訪問狀態(tài),簡單來說就是可以通過GlobalKey來調(diào)用子組件的方法或者屬性。
具體用法:
//在子組件的構(gòu)造方法中添加一個(gè)Key參數(shù)。并且調(diào)用super方法返回
ArticleListBaseWidget({Key key,this.cid}):super(key:key);
//在父組件中初始化組件,ArticleListBaseState為你要更改狀態(tài)的子組件
final GlobalKey<ArticleListBaseState> key = GlobalKey<ArticleListBaseState>();
//在父組件中初始化子組件的位置,將GlobalKey對象傳回到子組件
ArticleListBaseWidget(key:key,cid: cid));
//父組件中,在你需要更新子組件的位置利用GlobalKey對象調(diào)用子組件的方法
key.currentState.refreshController.requestRefresh();
在此運(yùn)行發(fā)現(xiàn),可以通過篩選條件來更新列表了。
浙公網(wǎng)安備 33010602011771號