flutter应用|flutter 开发一个应用 5, tab的bug修正,添加下拉上拉

分支切换到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了.

    推荐阅读