Flutter学习六之实现一个带筛选的列表页面

上期实现了一个网络轮播图的效果,自定义了一个轮播图组件,继承自StatefulWidget,我们知道Flutter中并没有像Android中activity的概念。页面见的跳转是通过路由从一个全屏组件跳转到另外的一个全屏组件,那如果我想在A组件中更新B组件的数据应该怎么实现呢?
今天我们来实现一个支持筛选的列表页面。前面我们已经实现来一个支持下拉刷新和上拉加载更多的列表组件,这里就不在做更多介绍来,效果图如下:

image

通过点击左滑菜单筛选列表的数据。由于列表在之前的一篇文章已经说明过Flutter学习四之实现一个支持刷新加载的列表,所以这里就直接上列表的代码:我们将列表定义程一个组件,方便,页面引用,列表的请求也放在组件内部。

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());
  }

//根据cid参数来处理apiName,如果没有传cid参数进来表示不支持分类筛选。
  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) {
      ///文章实体解析
      ArticleListEntityEntity articleBeanEntity =
          ArticleListEntityEntity().fromJson(jsonDecode(value.toString()));
      if (isRefresh) {
        _articles = articleBeanEntity.data.datas;
      } else {
        _articles.addAll(articleBeanEntity.data.datas);
      }

      ///接口调用成功后更新数据需要调用setState()方法
      setState(() {

      });

      if (articleBeanEntity.data.datas.length ==0) {
        //如果接口没有数据返回
        if (isRefresh) {
          //如果是下拉刷新的时候并且接口没有返回数据,隐藏列表控件,展示空页面
          refreshController.refreshFailed();
        } else {
          //如果是上啦加载更多并且没有返回数据,就展示列表控件,但是下拉提示没有更多数据了
          refreshController.loadNoData();
        }
      } else {
        //如果有数据返回,展示列表控件
        if (isRefresh) {
          //如果是下拉刷新,并且有数据返回正常展示页面数据
          refreshController.refreshCompleted();
        } else {
          //如果是上啦加载,并且有数据返回正常展示页面数据
          refreshController.loadComplete();
        }
      }
    }).catchError((onError) {
      if (isRefresh) {
        //如果下拉刷新,并且接口出错,展示错误页面
        refreshController.refreshFailed();
      } else {
        //如果上拉加载更多,并且接口出错,展示列表控件,底部下拉位置展示加载失败
        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)
    );
  }
}

因为要实现的列表支持筛选项,所以我们这里要在构造方法中接收一个cid参数,用来进行列表筛选的,代码很简单,主要是定义来一个下拉刷新和上拉加载更多要执行的方法,然后在接口返回中针对返回的数据处理来对应的刷新状态,以及对异常的处理,注释已经写的很清楚来。
接下来我们新建一个页面,

 @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("文章列表")),
        endDrawer: drawerSystem(),
        body:ArticleListBaseWidget(key:key,cid: cid));
  }

这里endDrawer方法是用来添加一个左滑菜单栏的,这里我们用来显示需要筛选的分类数据组件drawerSystem,该组件是由两个联动的列表组成的,同时在构造方法中,传人两个列表的点击时间,方便调用该组件的页面进行数据更新。这里要注意的是获取状态栏的高度和appbar的高度,还有我们要在endDrawer中自定义一个标题栏,但是endDrawer页面默认会有一个距离顶部的空白高度,我们要利用MediaQuery.removePadding方法来去掉顶部留白的部分,然后设置自己定义的标题栏。这里的appbar高度用常量kToolbarHeight来获取,MediaQuery.of(context).padding.top就是状态栏的高度,看到往上很多是直接写死,这样在不同的机型上面展示的效果会不一样的。
具体代码:

 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("体系数据"),
                    height: kToolbarHeight + MediaQuery.of(context).padding.top,
                    //appbar高度+状态栏高度
                    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的点击事件,这里为来方便在父组件进行数据的处理,所以是从item组件的构造方法将方法传进去的,

 ItemPageSystem(
      this.screenDataBean, this.screenChild, this.index, this.function);

然后就可以在点击事件处理筛选的联动效果了,

 //一级筛选点击事件处理
  onItemClick(int index) {
    setState(() {
      //全局记住点击位置
      this.index = index;
      //设置二级菜单数据集合
      screenChildList = screenList[index].children;
      //遍历一级数据设置一级菜单标示,是否选中
      updateListSelect(index, screenList);
    });
  }

  //二级筛选事件处理
  onItemClickChild(int index) {
    setState(() {
      //全局记住二级菜单点击位置
      indexChild = index;
      //双层循环遍历清空二级菜单为非选中状态
      for (int i = 0; i < screenList.length; i++) {
        updateListSelect(-1, screenList[i].children);
      }
      //设置当前点击数据为选中状态
      updateListSelect(index, screenChildList);
      cid = screenChildList[index].id;
      Navigator.pop(context);//关闭侧边菜单栏
      key.currentState.refreshController.requestRefresh();
    });
  }

到这一步基本已经完成了,我们看下效果发现,点击了筛选后并不能更新列表数据,这是为什么呢,查找了半天不知道问题处在哪里,通过查阅文档发现,如果想在父组件里面更新继承自StatefulWidget的组件的数据,光设置setStat还是不行,因为组件的Key是相同的没有改变,所以要想在父组件更新StatefulWidget组件的数据,需要用到GlobalKey,GlobalKey 能够跨 Widget 访问状态,简单来说就是可以通过GlobalKey来调用子组件的方法或者属性。
具体用法:

//在子组件的构造方法中添加一个Key参数。并且调用super方法返回
  ArticleListBaseWidget({Key key,this.cid}):super(key:key);

//在父组件中初始化组件,ArticleListBaseState为你要更改状态的子组件
  final GlobalKey<ArticleListBaseState> key = GlobalKey<ArticleListBaseState>();
  
 //在父组件中初始化子组件的位置,将GlobalKey对象传回到子组件
 ArticleListBaseWidget(key:key,cid: cid));
 
 //父组件中,在你需要更新子组件的位置利用GlobalKey对象调用子组件的方法
  key.currentState.refreshController.requestRefresh();

在此运行发现,可以通过筛选条件来更新列表了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,563评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,694评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,672评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,965评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,690评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,019评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,013评论 3 449
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,188评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,718评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,438评论 3 360
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,667评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,149评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,845评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,252评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,590评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,384评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,635评论 2 380