内容简介:状态主要有:loading,error,empty,以及展示内容的showContentbloc流供baseWidget做状态的变化主要提供了页面状态的Stream,提供子类使用,postPageEmpty2PageContent,postPageError 主要是的页面状态调用方法
状态主要有:loading,error,empty,以及展示内容的showContent
enum PageEnum { showLoading, showError, showEmpty, showContent, } 复制代码
1.2 定义一个枚举表示页面状态,另外还需定义事件的类,传递一些必要的数据
bloc流供baseWidget做状态的变化
class PageStatusEvent { String errorDesc; //错误数据,主要是展示错误页面 bool isRefresh;//主要用于list列表数据 PageEnum pageStatus; 页面状态 PageStatusEvent({this.errorDesc,this.isRefresh, this.pageStatus}); } 复制代码
1.3 BaseBloc封装
class BaseBloc { void dispose() { _pageEvent.close(); } ///请求专用的类 Repository repository = new Repository(); ///主要是事件通知 BehaviorSubject<PageStatusEvent> _pageEvent = BehaviorSubject<PageStatusEvent>(); get pageEventSink => _pageEvent.sink; get pageEventStream => _pageEvent.stream.asBroadcastStream(); postPageEmpty2PageContent(bool isRefresh, Object list) { pageEventSink.add(new PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: ObjectUtil.isEmpty(list) ? PageEnum.showEmpty : PageEnum.showContent)); } postPageError(bool isRefresh, String errorMsg) { pageEventSink.add( new PageStatusEvent(errorDesc : errorMsg, isRefresh: isRefresh, pageStatus: PageEnum.showError)); } } 复制代码
主要提供了页面状态的Stream,提供子类使用,postPageEmpty2PageContent,postPageError 主要是的页面状态调用方法
2.BaseWidget封装
@override Widget build(BuildContext context) { return _buildWidgetDefault(); } ///构建默认视图 Widget _buildWidgetDefault() { return WillPopScope( child: Scaffold( appBar: buildAppBar(), body: _buildBody(), ), ); } ///子类实现,构建各自页面UI控件 Widget buildWidget(BuildContext context); ///构建内容区 Widget _buildBody() { bloc = BlocProvider.of<B>(context); return new StreamBuilder( stream: bloc.pageEventStream, builder: (BuildContext context, AsyncSnapshot<PageStatusEvent> snapshot) { PageStatusEvent status; bool isShowContent = false; if (snapshot == null || snapshot.data == null) { isShowContent = false; status = PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showLoading); } else { status = snapshot.data; if ((!status.isRefresh) || (status.pageStatus == PageEnum.showContent && status.isRefresh)) { isShowContent = true; } else { isShowContent = false; } } return Container( ///内容区背景颜色 color: Colours.colorPrimaryWindowBg, child: Stack( children: <Widget>[ buildWidget(context), Offstage( offstage: isShowContent, child: getErrorWidget(status), ), ], ), ); }); } 复制代码
通过pageEventStream 事件来处理页面的状态,默认情况下展示loading状态,通过使用Stack 类似Android中的Framelayout帧布局来初始化loading页面和真正的业务布局。通过isShowContent来控制ErrorWidget视图的展示与否
Widget getErrorWidget(PageStatusEvent status) { current = status.pageStatus; if (status != null && status.isRefresh) { if (status.pageStatus == PageEnum.showEmpty) { return _buildEmptyWidget(); } else if (status.pageStatus == PageEnum.showError) { return _buildErrorWidget(status.errorDesc); } else { return _buildLoadingWidget(); } } return _buildLoadingWidget(); } 复制代码
错误页面的构建,可以自己自定义,详细的代码就不贴不来了,会根据status状态来返回对应的视图
void showLoadSuccess() { if (current != PageEnum.showContent) { current = PageEnum.showContent; //展示内容 bloc.pageEventSink .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showContent)); } } void showEmpty() { if (current != PageEnum.showEmpty) { current = PageEnum.showEmpty; //展示空页面 bloc.pageEventSink .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showEmpty)); } } void showError() { if (current != PageEnum.showError) { current = PageEnum.showError; //展示错误页面 bloc.pageEventSink .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showError)); } } void showLoading() { if (current != PageEnum.showLoading) { current = PageEnum.showLoading; //展示loading页面 bloc.pageEventSink .add(PageStatusEvent(errorDesc : "", isRefresh: true, pageStatus: PageEnum.showLoading)); } } 复制代码
另外还需要提供子类调用的四个状态更改的方法
3.bloc页面调用
class BarCodeBloc extends BaseBloc { final BehaviorSubject<String> _qrCodeController = BehaviorSubject<String>(); get onQrCodeSink => _qrCodeController.sink; get onQrCodeStream => _qrCodeController.stream; Future getQrCode(String custId) { repository.getQrCodeData(custId, onSuccess: (data) { onQrCodeSink.add(data); postPageEmpty2PageContent(true, data); }, onFailure: (error) { postPageError(true, error.errorDesc); }); } @override void dispose() { super.dispose(); _qrCodeController.close(); } } 复制代码
这是一个普通的二维码页面展示,根据api接口返回数据,直接调用postPageEmpty2PageContent用于展示业务布局,以及postPageError展示网络失败的布局
4.基本页面的使用逻辑
@override Widget buildWidget(BuildContext context) { return new StreamBuilder( stream: bloc.onQrCodeStream, builder: (BuildContext context, AsyncSnapshot<String> snapshot) { return Scaffold( body: Container( alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: <Widget>[ new QrImage( data: snapshot.data, size: Dimens.dp(200), ), ], ), ), ); }); } 复制代码
展示其实直接调用就可以了,不需要管理页面相关的状态逻辑,都是在父类帮忙完成了
5.List列表相关的封装
移动端开发很大一部分都是和ListView列表有点关,最好统一封装一下
5.1 上拉加载下拉刷新的控件封装
基于框架 pullToRefresh
//下拉刷新和上拉加载的回调 typedef void OnLoadMore(); typedef void OnRefresh(); class RefreshScaffold extends StatefulWidget { const RefreshScaffold( {Key key, @required this.controller, this.enablePullUp: true, this.enablePullDown: true, this.onRefresh, this.onLoadMore, this.child, this.bottomBar, this.headerWidget, this.itemCount, this.itemBuilder}) : super(key: key); final RefreshController controller; final bool enablePullUp; final bool enablePullDown; final OnRefresh onRefresh; final OnLoadMore onLoadMore; final Widget child; //底部按钮 final Widget bottomBar; //固定header的Widget final PreferredSize headerWidget; final int itemCount; final IndexedWidgetBuilder itemBuilder; @override State<StatefulWidget> createState() { return new RefreshScaffoldState(); } } /// with AutomaticKeepAliveClientMixin 用于保持列表的状态 class RefreshScaffoldState extends State<RefreshScaffold> with AutomaticKeepAliveClientMixin { @override void initState() { super.initState(); SchedulerBinding.instance.addPostFrameCallback((_) { widget.controller.requestRefresh(); }); } @override Widget build(BuildContext context) { super.build(context); return new Scaffold( appBar: widget.headerWidget, body: new SmartRefresher( controller: widget.controller, enablePullDown: widget.enablePullDown, enablePullUp: widget.enablePullUp, onRefresh: widget.onRefresh, onLoading: widget.onLoadMore, footer: ListFooterView(), header: MaterialClassicHeader(), child: widget.child ?? new ListView.builder( itemCount: widget.itemCount, itemBuilder: widget.itemBuilder, )), bottomNavigationBar: widget.bottomBar, ); } @override bool get wantKeepAlive => true; } 复制代码
主要是增加保持页面状态的wantKeepAlive回调,以及对应的一些页面header或者底部Bottom的封装
5.2 BaseListWidget封装
/// B:对应 BLoc 数据加载的Bloc /// E: 列表数据Entity abstract class BaseListState<T extends BaseListWidget, B extends BaseBloc, E extends Object> extends BaseState<T, B> { RefreshController controller = new RefreshController(); @override Widget buildWidget(BuildContext context) { bloc.pageEventStream.listen((PageStatusEvent event) { if (event.isRefresh) { controller.refreshCompleted(); //这句有必要的,实测不加上会导致加载更多无法回调 controller.loadComplete(); } else { if (event.pageStatus == PageEnum.showEmpty) { controller.loadNoData(); } else if (event.pageStatus == PageEnum.showError) { controller.loadFailed(); } else { controller.loadComplete(); } } }); return new StreamBuilder( stream: blocStream, builder: (BuildContext context, AsyncSnapshot<List<E>> snapshot) { return RefreshScaffold( controller: controller, enablePullDown: isLoadMore(), onRefresh: onRefresh, onLoadMore: onLoadMore, child: new ListView.builder( itemCount: snapshot.data == null ? 0 : snapshot.data.length, itemBuilder: (BuildContext context, int index) { E model = snapshot.data[index]; return buildItem(model); }, ), bottomBar: buildBottomBar(), headerWidget: buildHeaderWidget(), ); }); } ///默认存在分页 bool isLoadMore() { return true; } ///加载数据 get blocStream; ///刷新回调 void onRefresh(); ///加载回调 void onLoadMore(); ///构建Item Widget buildItem(E entity); @override void onErrorClick() { super.onErrorClick(); controller.requestRefresh(); } @override void dispose() { controller.dispose(); super.dispose(); } Widget buildBottomBar() { return null; } PreferredSize buildHeaderWidget() { return null; } 复制代码
提供三个泛型来控制布局的相关的数据,B对应Bloc,E对应的列表的实体类,提供了blocStream 的Bloc 刷新回调onRefresh,onLoadMore加载更多回调,构建item的回调等
pageEventStream的监听主要处理下来刷新和加载更多的逻辑
bloc.pageEventStream.listen((PageStatusEvent event) { if (event.isRefresh) { controller.refreshCompleted(); //这句有必要的,实测不加上会导致加载更多无法回调 controller.loadComplete(); } else { if (event.pageStatus == PageEnum.showEmpty) { controller.loadNoData(); } else if (event.pageStatus == PageEnum.showError) { controller.loadFailed(); } else { controller.loadComplete(); } } }); 复制代码
5.3 普通的列表调用方式
只需要实现对应的方法即可,代码就比较清爽了
class _LoanVisitPageState extends BaseListState<LoanVisitPage, LoanVisitBloc, TaskEntity> { final String labelId; _LoanVisitPageState(this.labelId); @override void onRefresh() { bloc.onRefresh(labelId: labelId); } @override void onLoadMore() { bloc.onLoadMore(labelId: labelId); } @override String setEmptyMsg() { return Ids.noVisitTask; } @override get blocStream => bloc.loanVisitStream; @override Widget buildItem(TaskEntity entity) { return new LoanVisitItem(entity); } } 复制代码
5.4 list普通列表的bloc调用
class LoanVisitBloc extends BaseBloc { BehaviorSubject<List<TaskEntity>> _loanVisit = BehaviorSubject<List<TaskEntity>>(); get _loanVisitSink => _loanVisit.sink; get loanVisitStream => _loanVisit.stream; List<TaskEntity> _reposList = new List(); int _taskPage = 1; //列表数据请求 Future getLoanVisitList(String labelId, int page) { bool isRefresh; if (page == 1) { _reposList.clear(); isRefresh = true; } else { isRefresh = false; } return repository.getVisitList(NetApi.RETURN_VISIT, page, 20, onSuccess: (list) { _reposList.addAll(list); _loanVisitSink.add(UnmodifiableListView<TaskEntity>(_reposList)); postPageEmpty2PageContent(isRefresh, list); }, onFailure: (error) { postPageError(isRefresh, error.errorDesc); }); } @override void dispose() { _loanVisit.close(); } @override Future getData({String labelId, int page}) { return getLoanVisitList(labelId, page); } @override Future onLoadMore({String labelId}) { _taskPage +=1 ; return getData(labelId: labelId, page: _taskPage); } @override Future onRefresh({String labelId}) { _taskPage = 1; return getData(labelId: labelId, page: 1); } } 复制代码
提供刷新和加载更多的方法,也是比较一般的请求
6.总结
借鉴了很多网上的文章,并且结合项目做了修改,bloc的好处避免了setState的损耗,对于页面的状态的管理是很好的,后续会提供一个demo供参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 封装JDBC—非框架开发必备的封装类
- Mybatis: 动手封装ORM框架
- 自定义MVC框架-封装模型层
- 优雅地实现Android主流图片加载框架封装,可无侵入切换框架
- 基于spring boot框架进行二次封装,微型框架编写思路
- 自定义MVC框架 -封装控制器层
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML5秘籍(第2版)
[美] Matthew MacDonald / 李松峰、朱巍、刘帅 / 人民邮电出版社 / 2015-4 / 89.00元
不依赖插件添加音频和视频,构建适用于所有浏览器的播放页面。 用Canvas创建吸引人的视觉效果,绘制图形、图像、文本,播放动画,运行交互游戏。 用CSS3将页面变活泼,比如添加新奇的字体,利用变换和动画添加吸引人的效果。 设计更出色的Web表单,利用HTML5新增的表单元素更加高效地收集访客信息。 一次开发,多平台运行,实现响应式设计,创建适配桌面计算机、平板电脑和智能手机......一起来看看 《HTML5秘籍(第2版)》 这本书的介绍吧!