Flutter 之列表和头部 (ListView + Header)

上一篇介绍Banner的开发。在大多数应用场景中。banner和ListView通常是一起显示的。 并且能够共同滑动。例如如下界面:


Flutter 之列表和头部 (ListView + Header)_第1张图片
421896775.jpg

要实现上图的界面,直接想到是ListView添加Header。但在Flutter中,ListView 组件相当于RecyclerView,所以添加Header也用RecyclerView的原理:

封装ListPage组件,list_page.dart

import 'package:flutter/material.dart';

typedef HeaderWidgetBuild = Widget Function(BuildContext context, int position);

typedef ItemWidgetBuild = Widget Function(BuildContext context, int position);

class ListPage extends StatefulWidget {
  List headerList;
  List listData;
  ItemWidgetBuild itemWidgetCreator;
  HeaderWidgetBuild headerCreator;

  ListPage(List this.listData,
      {Key key,
      List this.headerList,
      ItemWidgetBuild this.itemWidgetCreator,
      HeaderWidgetBuild this.headerCreator})
      : super(key: key);

  @override
  ListPageState createState() {
    return new ListPageState();
  }
}

class ListPageState extends State {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: new ListView.builder(
          itemBuilder: (BuildContext context, int position) {
            return buildItemWidget(context, position);
          },
          itemCount: _getListCount()),
    );
  }

  int _getListCount() {
    int itemCount = widget.listData.length;
    return getHeaderCount() + itemCount;
  }

  int getHeaderCount() {
    int headerCount = widget.headerList != null ? widget.headerList.length : 0;
    return headerCount;
  }

  Widget _headerItemWidget(BuildContext context, int index) {
    if (widget.headerCreator != null) {
      return widget.headerCreator(context, index);
    } else {
      return new GestureDetector(
        child: new Padding(
            padding: new EdgeInsets.all(10.0),
            child: new Text("Header Row $index")),
        onTap: () {
          print('header click $index --------------------');
        },
      );
    }
  }

  Widget buildItemWidget(BuildContext context, int index) {
    if (index < getHeaderCount()) {
      return _headerItemWidget(context, index);
    } else {
      int pos = index - getHeaderCount();
      return _itemBuildWidget(context, pos);
    }
  }

  Widget _itemBuildWidget(BuildContext context, int index) {
    if (widget.itemWidgetCreator != null) {
      return widget.itemWidgetCreator(context, index);
    } else {
      return new GestureDetector(
        child: new Padding(
            padding: new EdgeInsets.all(10.0), child: new Text("Row $index")),
        onTap: () {
          print('click $index --------------------');
        },
      );
    }
  }
}

使用及测试:异步加载网络数据使用

import 'package:demonewsapp/app_constance.dart';
import 'package:demonewsapp/common/banner_widget.dart';
import 'package:demonewsapp/common/str_util.dart';
import 'package:demonewsapp/page/list_page.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:transparent_image/transparent_image.dart';

class NewsListPage extends StatefulWidget {
  @override
  NewsListPageState createState() {
    return NewsListPageState();
  }
}

class NewsListPageState extends State {
  List bannerList = [];

  List newsList = [];

  @override
  void initState() {
    super.initState();
    _getBannerData();
    _testNewsData();
    _getNewsListData();
  }

  _testNewsData() {
    for (int i = 0; i < 10; i++) {
      NewsItem news = new NewsItem();
      news.nid = i;
      news.nodeTitle =
          "$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i$i";
      news.node_created = 12345;
      news.total_count = '1111';
      news.node_type = 'news';
      news.thumbPath =
          '''http://img.mukewang.com/user/584ff8bf0001609c01000100-100-100.jpg''';
      news.news_category = 'test';

      newsList.add(news);
    }
  }

  _getNewsListData() async {
    String url = 'http://www.wsrtv.com.cn/services/sevice_news_list.json';
    var res = await http.get(url);
    List resList = json.decode(res.body);
    List tempList = [];
    for (var item in resList) {
      NewsItem news = new NewsItem();
      news.nid = int.parse(item['nid']);
      news.nodeTitle = item['node_title'];
      news.node_created = int.parse(item['node_created']);
      news.total_count = item['totalcount'];
      news.node_type = item['node_type'];
      news.thumbPath =
          StringUtil.getSrcImagePath(item['field_news_video_thumb_app']);
      var field_news_category = item['field_news_category'];
      if (field_news_category is List) {
        news.news_category = field_news_category[0].toString();
      } else {
        news.news_category = field_news_category.toString();
      }
      print(news.news_category);
      tempList.add(news);
    }

    setState(() {
      newsList = tempList;
    });
  }

  _getBannerData() async {
    String url =
        AppConstance.makeUrl('services/service_news_slideshow.json', null);
    var res = await http.get(url);
    List list = json.decode(res.body);
    List temp = [];
    for (var item in list) {
      String imagePath = item['field_new_app_slideshow'];
      imagePath = StringUtil.getSrcImagePath(imagePath);
      String text = item['node_title'];
      temp.add(BannerItem.defaultBannerItem(imagePath, text));
    }

    print('temp ==== $temp');
    bannerList = temp;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: Text('news list and banner'),
      ),
      body: new ListPage(
        newsList,
        headerList: [1, 2],
        itemWidgetCreator: getItemWidget,
        headerCreator: (BuildContext context, int position) {
          if(position == 0) {
            return new BannerWidget(180.0, bannerList);
          }else {
            return new Padding(padding: EdgeInsets.all(10.0), child: 
              Text('$position -----header------- '),);
          }
        },
      ),
    );
  }

  _onItemClick(int pos) {
    if (newsList != null && newsList.length > pos) {
      print('click $pos ==== ${newsList[pos].nid}');
    }
  }

  Widget getItemWidget(BuildContext context, int pos) {
    return new GestureDetector(
      onTap: () {
        _onItemClick(pos);
      },
      child: IntrinsicHeight(
        child: Container(
          height: 80.0,
          padding: EdgeInsets.all(7.0),
          decoration: UnderlineTabIndicator(
              borderSide: BorderSide(color: Color(0Xfff1f1f1), width: 1.0)),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              AspectRatio(
                aspectRatio: 118 / 66,
                child: FadeInImage.memoryNetwork(
                  placeholder: kTransparentImage,
                  image: newsList[pos].thumbPath,
                  fit: BoxFit.cover,
                ),
              ),
              Expanded(
                flex: 1,
                child: Padding(
                  padding: EdgeInsets.only(left: 7.0),
                  child: Column(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      Expanded(
                          child: Text(
                        newsList[pos].nodeTitle,
                        style: new TextStyle(
                          color: Colors.black,
                          fontSize: 15.0,
                          decoration: TextDecoration.none,
                        ),
                        softWrap: true,
                        maxLines: 2,
                        overflow: TextOverflow.ellipsis,
                      )),
                      Container(child: getItemBottomWidget(pos)),
                    ],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }

  Widget getItemBottomWidget(int pos) {
    return IntrinsicHeight(
      child: Row(
        mainAxisSize: MainAxisSize.max,
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          Expanded(
              child: Text(
            newsList[pos].news_category,
            style: TextStyle(
                color: Color(0xff979797),
                fontSize: 12.0,
                decoration: TextDecoration.none),
          )),
          Container(
            padding: EdgeInsets.all(3.0),
            decoration: ShapeDecoration(
                shape: RoundedRectangleBorder(
                    side: BorderSide(color: Color(0xfff1f1f1)),
                    borderRadius: BorderRadius.circular(3.0)),
                color: Color(0xfff1f1f1)),
            child: Text(
              '${newsList[pos].total_count}浏览',
              style: TextStyle(
                  color: Color(0xff979797),
                  fontSize: 12.0,
                  decoration: TextDecoration.none),
            ),
          ),
        ],
      ),
    );
  }
}

class NewsItem {
  int nid;
  String nodeTitle;
  String total_count;
  int node_created;
  String node_type;
  String thumbPath;
  String news_category;
}

你可能感兴趣的:(Flutter 之列表和头部 (ListView + Header))