分支切换到dev_1.1了.
当切换多个tab的时候,会发现,原来的列表又初始化了一次.这个解决也简单,就是使用胶水类.
class _GankJsonListPageState extends State
with AutomaticKeepAliveClientMixin {//加上这一句,
重写
@override
bool get wantKeepAlive => true;
}
这样就行了.这样做无疑会增加内存的消耗,如果页面比较多.
再将原来的列表项拆分:
Widget buildRow(int i) {
var beans = gankToday.beans;
//print('bean i:$i data:$beans');
if (beans == null) {
return Text("no items:");
}
var bean = beans[i];
if (bean.images == null || bean.images.length < 1) {
return GankListNoImageItem(
bean: bean,
onPressed: () {
detail(bean);
});
} else {
return GankListImageItem(
bean: bean,
onPressed: () {
detail(bean);
});
}
}
有图的项:
class GankListImageItem extends StatelessWidget {
GankListImageItem({Key key, this.bean, this.onPressed}) : super(key: key);
final GankBean bean;
final VoidCallback onPressed;
void detail(GankBean bean) {}@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
onPressed();
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
child: Text("Title:${bean.publishedAt}")),
Padding(
padding: EdgeInsets.only(
left: 10.0, right: 10.0, top: 5.0, bottom: 5.0),
child: Text("Url:${bean.images[0]}")),
Padding(
padding: EdgeInsets.only(left: 10.0, right: 10.0),
child: Image(
image: CachedNetworkImageProvider(bean.images[0]),
//width: album.images.width.toDouble(),
//height: album.images.height.toDouble(),
fit: BoxFit.fitWidth,
),
),
],
),
);
}
}
无图的就不粘贴了,上github看源码就行了.
【flutter应用|flutter 开发一个应用 5, tab的bug修正,添加下拉上拉】如果添加进了SmartRefresher,在加载图片时,没有设置图片的高与宽,会出现RenderFlex overflowed异常.
所以,上拉与下拉的列表,就不在这修改了.
建立一个PullWidget.
这里参考了gsy的代码.
class PullWidget extends StatefulWidget {
PullWidget(
{Key key,
this.pullController,
this.items,
this.itemBuilder,
this.onLoadMore,
this.onRefresh})
: super(key: key);
final List items;
final PullWidgetController pullController;
final IndexedWidgetBuilder itemBuilder;
final RefreshCallback onLoadMore;
final RefreshCallback onRefresh;
@override
_PullWidgetState createState() => new _PullWidgetState();
}class _PullWidgetState extends State {
RefreshController _refreshController =
RefreshController(initialRefresh: false);
ScrollController _scrollController;
_PullWidgetState() : super();
@override
void initState() {
super.initState();
_scrollController = new ScrollController();
widget.pullController.needLoadMore?.addListener(() {
_refreshController.loadComplete();
加载完成,通过它刷新,如果列表项是StatefulWidget ,则无法刷新数据.
});
widget.pullController.needRefresh?.addListener(() {
_refreshController.refreshCompleted();
});
///增加滑动监听
_scrollController.addListener(() {
///判断当前滑动位置是不是到达底部,触发加载更多回调
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
if (widget.pullController.needLoadMore.value) {
widget.onLoadMore?.call();
}
}
});
}@override
void dispose() {
super.dispose();
_refreshController.dispose();
}void _onRefresh() async {
// monitor network fetch
/*await Future.delayed(Duration(milliseconds: 1000));
// if failed,use refreshFailed()
_refreshController.refreshCompleted();
*/
widget.pullController.needRefresh.value = https://www.it610.com/article/false;
widget.onRefresh?.call();
}void _onLoading() async {
// monitor network fetch
/*await Future.delayed(Duration(milliseconds: 1000));
// if failed,use loadFailed(),if no data return,use LoadNodata()
widget.items.add((widget.items.length + 1).toString());
if (mounted) setState(() {});
_refreshController.loadComplete();
*/
widget.pullController.needLoadMore.value = false;
widget.onLoadMore?.call();
}@override
Widget build(BuildContext context) {
return Scaffold(
body: SmartRefresher(
enablePullDown: true,
enablePullUp: true,
header: MaterialClassicHeader(),
footer: CustomFooter(
builder: (BuildContext context, LoadStatus mode) {
Widget body;
if (mode == LoadStatus.idle) {
body = Text("pull up load");
} else if (mode == LoadStatus.loading) {
body = CircularProgressIndicator();
} else if (mode == LoadStatus.failed) {
body = Text("Load Failed!Click retry!");
} else {
body = Text("No more Data");
}
return Container(
height: 55.0,
child: Center(child: body),
);
},
),
controller: _refreshController,
onRefresh: _onRefresh,
onLoading: _onLoading,
child: new ListView.builder(
///保持ListView任何情况都能滚动,解决在RefreshIndicator的兼容问题。
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return _getItem(index);
},itemExtent: 100.0,
itemCount: _getListCount(),
controller: _scrollController,
),
),
);
}int _getListCount() {
return widget.items.length;
}_getItem(int index) {
return widget.itemBuilder(context, index);
//return Card(child: Center(child: Text(items[index])));
}
}class PullWidgetController {
List dataList = new List();
ValueNotifier needLoadMore = new ValueNotifier(false);
ValueNotifier needRefresh = new ValueNotifier(false);
}
建立一个测试页::
class TestListPage extends StatefulWidget {
TestListPage({Key key, this.title}) : super(key: key);
final String title;
@override
_TestListPageState createState() => new _TestListPageState();
}class _TestListPageState extends State
with AutomaticKeepAliveClientMixin {
List items = [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
];
PullWidgetController _pullController = new PullWidgetController();
@override
void initState() {
super.initState();
_pullController.dataList = items;
}@override
bool get wantKeepAlive => true;
int page = 1;
bool isLoading = false;
bool isRefreshing = false;
bool isLoadMoring = false;
_lockToAwait() async {
///if loading, lock to await
doDelayed() async {
await Future.delayed(Duration(seconds: 1)).then((_) async {
if (isLoading) {
return await doDelayed();
} else {
return null;
}
});
}await doDelayed();
}@protected
Future handleRefresh() async {
if (isLoading) {
if (isRefreshing) {
return null;
}
await _lockToAwait();
}
isLoading = true;
isRefreshing = true;
page = 1;
var res = await requestRefresh();
print("res:$res;
");
if (res != null) {
resolveRefreshResult(res);
setState(() {
_pullController.needRefresh.value =https://www.it610.com/article/true;
});
}
isLoading = false;
isRefreshing = false;
return null;
}@protected
resolveRefreshResult(res) {
if (res != null) {
_pullController?.dataList?.clear();
setState(() {
_pullController?.dataList?.addAll(res);
//print("resolveRefreshResult:$res;
");
});
}
}@protected
Future onLoadMore() async {
if (isLoading) {
if (isLoadMoring) {
return null;
}
await _lockToAwait();
}
isLoading = true;
isLoadMoring = true;
page++;
var res = await requestLoadMore();
if (res != null) {
setState(() {
_pullController?.dataList?.addAll(res);
});
}
setState(() {
_pullController.needLoadMore.value = https://www.it610.com/article/(res != null);
});
isLoading = false;
isLoadMoring = false;
return null;
}//下拉刷新数据
@protected
requestRefresh() async {
return ["11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
];
}///上拉更多请求数据
@protected
requestLoadMore() async {
return [(_pullController.dataList.length + 1).toString()];
}_renderItem(int index) {
if (_pullController.dataList.length == 0) {
return null;
}
//print("item:${_pullController.dataList[index]}");
switch (index) {
case 2:
return new TestListItem(bean: _pullController.dataList[index], onPressed: () {});
default:
return new TestListItem(bean: _pullController.dataList[index], onPressed: () {});
}
}//关键部分,把这个参数传入,这样,pullwidget是可重用的. 外部关心的是加载数据与渲染列表项.
@override
Widget build(BuildContext context) {
return new PullWidget(
pullController: _pullController,
items: _pullController.dataList,
itemBuilder: (BuildContext context, int index) => _renderItem(index),
onLoadMore: onLoadMore,
onRefresh: handleRefresh,
);
}
}
列表项就是普通的控件
class TestListItem extends StatelessWidget {
TestListItem({this.bean, this.onPressed}) : super();
final String bean;
final VoidCallback onPressed;
void detail(String bean) {}@override
Widget build(BuildContext context) {
return Card(child: Center(child: Text(bean)));
}
}
把这两个 列表放到TabBarPageWidget里面;
_renderPage() {
return [
new GankJsonListPage(),
new TestListPage(),
new TestListPage(),
new TestListPage(),
];
}
就样,就可以切换tab了.
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- 程序员|DevEcoStudio的及其传感器的使用,闭关在家37天“吃透”这份345页PDF
- 图片加载框架之 ImageLoader
- OKHttp源码分析1 - 框架
- 移动开发|android 录屏方案 VFR和CFR
- 配置Android Studio的缓存文件路径(释放你的C盘)
- android Module之间数据传递
- 移动开发|Android入门(5)最简单的Handler例子
- 移动开发|我在达内学安卓-基于ArrayAdapter创建单一对象列表方式创建联系人列表!
- 移动开发|Android消息传递之EventBus 3.0使用详解