目的:学习 LceermWidget 的使用。
创建一个 widget 继承 LceermWidget,分别渲染 4中状态的页面,点击按钮重新加载数据, 上滑加载更多,下拉刷新。
Lcee 本章第一节已经介绍过了,rm 分别表示 refresh、 more。 这是最常见的需求,虽然实现起来不难,但代码写多了之后就发现,大部分都是重复的代码,但让人头疼的是,要提取出来并不容易,幸好我们有了 mvvm 框架,这让抽象和封装状态变得容易多了。
实际上看到这里,肯定有大佬会提出一个问题:rm 状态实际上应该是属于 content 里面的内容,为什么要放到同一个层级呢?
实际上这个问题我也想了很久,从实现上来说,放在同一个层级确实方便很多,因为他们之间关系太过紧密,强行分开需要额外的代价。 但作为一个较真的程序员,实际上,本库单独提供了一个 RmWidget
,下一节就会介绍用 RmWidget
+ LceeWidget
实现本节一样的功能。
和上一节基本相同,只是增加了下拉刷新和加载更多。
新建 UserLceermWidget
,并复制本章第一节的 WidgetActivity
,修改里面的 wiget 引用。
新建 UserLceeWidget
继承 LceermWidget
。
需要实现 renderLoading
、renderContent
、renderEmpty
、renderError
、onLoadData
、 onLoadMore
、 onRefreshError
、 onRefreshEmpty
、 onRefreshComplete
、 onLoadMoreError
、 onLoadMoreEmpty
、 onLoadMoreComplete
等方法。
大致意思从方法名就可以知道。
renderLoading
、renderEmpty
、renderError
这 3 个简单的和第一节的一样,不再重复。
比第一节多加了一个 SwipeRefreshWidget
private SwipeRefreshWidget srw;
private SwipeRefreshLayout.OnRefreshListener refresh = () -> {
if (!refresh()) {
srw.refreshing(false);
}
};
@Override
protected Widget renderContent() {
srw = new SwipeRefreshWidget(context, lifecycle,
renderRecycler()
)
.onRefreshListener(refresh)
.matchParent();
return new FrameWidget(context, lifecycle,
srw,
renderFab()
);
}
获取数据和第一节及其类似,但这里为了标记没有更多数据了,加了一个变量 noMoreData
,这个并不是必要的,这里只是因为数据源的数据都是随机的,所以不得不这么做。
private List<UserBean> data = Collections.emptyList();
private boolean noMoreData;
@Override
protected LceeStatus onLoadData() throws NetworkErrorException {
noMoreData = false;
data = UserDataSource.getInstance().getUsersFromRemote();
if (data.isEmpty())
return LceeStatus.Empty;
return LceeStatus.Content;
}
@Override
protected LceeStatus onLoadMore() throws NetworkErrorException {
data = UserDataSource.getInstance().getUsersFromRemote();
if (data.isEmpty()) {
noMoreData = true;
return LceeStatus.Empty;
}
return LceeStatus.Content;
}
刷新和加载更多 也难免会失败,但失败之后,一般来说并不需要重新渲染一个新的页面,通常是弹出一个提示,所以这里封装的 api 也是这么考虑的。
@Override
protected void onRefreshError(Throwable throwable) {
Toast.makeText(context, "Refresh failed !", Toast.LENGTH_SHORT).show();
lastError.printStackTrace();
}
@Override
protected void onRefreshComplete() {
srw.refreshing(false);
}
@Override
protected void onLoadMoreError(Throwable throwable) {
Toast.makeText(context, "Load more data failed !", Toast.LENGTH_SHORT).show();
lastError.printStackTrace();
}
@Override
protected void onLoadMoreEmpty() {
Toast.makeText(context, "No more data !", Toast.LENGTH_SHORT).show();
}
@Override
protected void onLoadMoreComplete() {
// change load more UI if necessary
}
下拉刷新是通过 SwipeRefreshLayout
来实现的,而加载更多官方并没有提供这么便利的东西,出于让本 demo 尽量简单少依赖,直接复制粘贴了网上一个简单的实现,虽然简陋,但能用。 各位开发者采用的实现方式各不相同,你需要关心的时候如何触发加载更多。
实际上很简单,调用方法 loadMore()
即可。
更新数据时,你需要判断 loadType
来决定是 setData
还是 addData
。
private RecyclerWidget renderRecycler() {
return new RecyclerWidget<UserItemAdapter>(context, lifecycle) {
@Override
protected void initProps() {
width = matchParent;
height = matchParent;
layoutManager = new LinearLayoutManager(context);
adapter = new UserItemAdapter(lifecycle);
// load more
view.addOnScrollListener(new RecyclerView.OnScrollListener() {
private int mLastVisibleItemPosition;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (adapter == null || noMoreData || status == LceeStatus.Loading)
return;
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
mLastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
}
if (newState == RecyclerView.SCROLL_STATE_IDLE
&& mLastVisibleItemPosition + 1 == adapter.getItemCount()) {
loadMore();
}
}
});
}
@Override
public void update() {
super.update();
// attention: must check status before update view data
if (status != LceeStatus.Content)
return;
switch (loadType) {
case FirstLoad:
case Refresh:
adapter.setData(data);
break;
case LoadMore:
adapter.addData(data);
break;
}
}
};
}
除了新增的 LoadType,其他和第一节基本一样。 在这样高度封装之后,大大减少了状态管理的重复代码,让你可以专注于业务,同时也可以提高代码可读性。