上一篇介绍Banner的开发。在大多数应用场景中。banner和ListView通常是一起显示的。 并且能够共同滑动。例如如下界面:
要实现上图的界面,直接想到是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<ListPage> {
@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<NewsListPage> {
List<BannerItem> bannerList = [];
List<NewsItem> 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<NewsItem> 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<BannerItem> 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: <Widget>[
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: <Widget>[
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: <Widget>[
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;
}