Flutter 基于Bloc框架的封装

栏目: IOS · Android · 发布时间: 5年前

内容简介:状态主要有: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供参考


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

瞬间之美

瞬间之美

[美] Robert Hoekman, Jr. / 向怡宁 / 人民邮电出版社 / 2009-7 / 45.00元

本书特色: 本书通过重现用户面对Web应用时由始至终的完整情境,主要针对Web应用中几乎所有相关元素,例如Search、Screencast、Blog 、Wizard、 RSS、 Rate 、TagCloud 以及Form的布局、交互甚至客服等都提出了值得借鉴的解决方案。具体通过30多个故事轻松自然地带领读者领会设计者如何百分之百地用心传达以创造美好的用户体验。 本书适用于信息架构设计......一起来看看 《瞬间之美》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码