想选一个刷新加载 又可以添加各种header 的列表控件,挑来挑去也就easyrecyclerview 最好用了,
可是刷新加载 却也有bug
- 1.刷新的时候不能加载,加载的时候不能刷新,解决刷新的时候不能加载(我的方案给个变量isRefreshing 刷新的时候为true
加载回掉接口的时候,如果是true就不让他加载),解决加载的 时候不能刷新(弹出进度对话框)这两种解决方案比较恶心,需要
写在界面的代码里,这是我不愿意看到的 - 第二个bug,我在项目里算是bug,每次刷新数据的时候,都会自动去加载,每次进入界面不光调用刷新的接口,也会调用加载的
接口。 - 3.第三个缺陷,不能定制刷新的样式,和加载的样式,这个我后面讲怎么去改它
EasyRecyclerView继承FrameLayout,
填充了下面的布局代码
<com.jude.easyrecyclerview.swipe.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ptr_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical|horizontal"
android:clickable="true"/>
<FrameLayout
android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
/>
<FrameLayout
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
/>
<FrameLayout
android:id="@+id/error"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
/>
</com.jude.easyrecyclerview.swipe.SwipeRefreshLayout>
- 所以你可以设置刚进入前面时候的布局id=progress
数据为空的时候的布局 id = empty
发生错误的时候布局 id = error
这个控件的功能简直和它的名字一样感人easy
public void setAdapterWithProgress(RecyclerView.Adapter adapter) {
mRecycler.setAdapter(adapter);
adapter.registerAdapterDataObserver(new EasyDataObserver(this));
//只有Adapter为空时才显示ProgressView
if (adapter instanceof RecyclerArrayAdapter){
if (((RecyclerArrayAdapter) adapter).getCount() == 0){
showProgress();
}else {
showRecycler();
}
}else {
if (adapter.getItemCount() == 0){
showProgress();
}else {
showRecycler();
}
}
}
如果数据为0的时候加载showProgress()方法
如果数据不是0 显示recyclerview下面先介绍一下刷新的原理
刷新是通过 swiperefreshlayout,这个应该support v4包下面的一个控件
通过serRefresh方法设置它是否刷新和刷新完毕
我们通过在EasyRecyclerView类里面找到这个方法,基本就可以跟踪到它是怎么刷新的了,结果你会发现你只找到了一处调用setrefresh(false)的方法
private void hideAll(){
mEmptyView.setVisibility(View.GONE);
mProgressView.setVisibility(View.GONE);
mErrorView.setVisibility(GONE);
mPtrLayout.setRefreshing(false);
mRecycler.setVisibility(View.INVISIBLE);
}
没有调用setrefresh(true)的方法 wtf,没关系继续跟踪这个方法的类swipefreshlayou
你会发现一个方法
public boolean dispatchTouchEvent(MotionEvent ev) {
return mPtrLayout.dispatchTouchEvent(ev);
}
easyrecylerview的事件传递给了swiperefreshlayou了,所以推断setrefresh(true)
应该是它自己根据滑动事件去处理的。
我们接下来在swperefreshlayout里面去找setrefresh(true)的方法
发现了被这个方法调用:
private void finishSpinner(float overscrollTop) {
if (overscrollTop > mTotalDragDistance) {
setRefreshing(true, true /* notify */);
} else {
// cancel refresh
mRefreshing = false;
mProgress.setStartEndTrim(0f, 0f);
AnimationListener listener = null;
if (!mScale) {
listener = new AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
if (!mScale) {
startScaleDownAnimation(null);
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
};
}
animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
mProgress.showArrow(false);
}
}
跟踪finishSpinner的方法发现两处调用onTouchEvent 和onStopNestedScroll
证明猜想正确,别问我为什么不分析这些事件代码,因为我还没有改它的需求,所以我就避开了这个麻烦。
基本确定setrefresh(true)根据滑动事件主动触发
下面我们去看看setrefresh(false)方法什么时候调用呢
上面说了hidall 接着就去跟踪hideall方法,
被这两个方法调用
public void showProgress() {
log("showProgress");
if (mProgressView.getChildCount()>0){
hideAll();
mProgressView.setVisibility(View.VISIBLE);
}else {
showRecycler();
}
}
public void showRecycler() {
log("showRecycler");
hideAll();
mRecycler.setVisibility(View.VISIBLE);
}
public void showEmpty() {
log("showEmpty");
if (mEmptyView.getChildCount()>0){
hideAll();
mEmptyView.setVisibility(View.VISIBLE);
}else {
showRecycler();
}
}
接着你会发现这两个方法会被setAdapterWithProgress这个方法 调用,这个是设置adapter的方法 ,怎么可能,我可是想要寻求停止刷新的
方法,这个只会在初始化的时候调用一次啊,说明还有其他地方调用了,我们看看findUsages 找找。
你会发先easydataObserver类里面有一个update方法
//自动更改Container的样式
private void update() {
int count;
if (recyclerView.getAdapter() instanceof RecyclerArrayAdapter) {
count = ((RecyclerArrayAdapter) recyclerView.getAdapter()).getCount();
} else {
count = recyclerView.getAdapter().getItemCount();
}
if (count == 0) {
recyclerView.showEmpty();
} else {
recyclerView.showRecycler();
}
}
class EasyDataObserver extends RecyclerView.AdapterDataObserver
这个类继承了 RecyclerView.AdapterDataObserver ,此时你需要弄清一个原理
recyclerView 和listview一样数据填充都是通过adapter,adapter的实现通过观察者模式,数据更新的时候会发一个通知
注册了这个通知的类就会收到数据更新的通知。
我们跟踪EasyDataObserver 这个类看看,
public void setAdapter(RecyclerView.Adapter adapter) {
...
adapter.registerAdapterDataObserver(new EasyDataObserver(this));
...
}
/**
* 设置适配器,关闭所有副view。展示进度条View
* 适配器有更新,自动关闭所有副view。根据条数判断是否展示EmptyView
*
* @param adapter
*/
public void setAdapterWithProgress(RecyclerView.Adapter adapter) {
mRecycler.setAdapter(adapter);
adapter.registerAdapterDataObserver(new EasyDataObserver(this));
...
}
看见了吧设置adapter的时候 会注册一个数据更新的监听器
所以每次adapter 添加数据的时候都会通知EasyDataObserver,然后调用update
再去调用showempty或者showrecycer方法 他们里面会有一个hideall方法 调用setrefres(false)去取消刷新
我们看看RecyclerArrayAdapter 里面添加数据的方法 他们总会调用一个方法notifyItemInserted 去给EasyDataObserver
发送通知
public void add(T object) {
...
if (mNotifyOnChange) notifyItemInserted(headers.size()+getCount());
...
}
public void addAll(Collection<? extends T> collection) {
...
if (mNotifyOnChange) notifyItemRangeInserted(headers.size()+getCount()-dataCount,dataCount);
...
}
public void addAll(T[] items) {
....
if (mNotifyOnChange) notifyItemRangeInserted(headers.size()+getCount()-dataCount,dataCount);
...
}
到这里刷新的 原理就搞定了,
下面我介绍加载的原理
在介绍之前我得说一个加载的时候的bug 之前提到的 bug2
当你的数据条数没有填充全屏的时候,就会调用一次加载接口,第二次填充的数据没有填充全屏,继续调用加载的回调
这个bug看你的需求,反正对我来说是很恶心的东西,
加载是完全通过RecyclerArrayAdapter去控制的,所以加载刷新是两个相互独立的东西,这就造成了刷新的时候是可以加载的,
加载的时候可以刷新的时候bug
你要实现加载功能需要调用下面方法
public void setMore(final int res, final OnLoadMoreListener listener){
getEventDelegate().setMore(res, new OnMoreListener() {
@Override
public void onMoreShow() {
listener.onLoadMore();
}
@Override
public void onMoreClick() {
}
});
}
你会发先onLoadMoreListener 传递给了EventDelegate 一个代理的类
我们在看一下getEventDelegate方法
EventDelegate getEventDelegate(){
if (mEventDelegate == null)mEventDelegate = new DefaultEventDelegate(this);
return mEventDelegate;
}
这个代理的类有个默认的实现,也可以自己去拓展实现它的接口,不过你必须按照它规定的原理去实现所以我们去找
DefaultEventDelegate 的setMore方法
@Override
public void setMore(int res, RecyclerArrayAdapter.OnMoreListener listener) {
this.footer.setMoreViewRes(res);
this.onMoreListener = listener;
hasMore = true;
// 为了处理setMore之前就添加了数据的情况
if (adapter.getCount()>0){
addData(adapter.getCount());
}
log("setMore");
}
你会发先一个方法addData()
@Override
public void addData(int length) {
log("addData" + length);
if (hasMore){
if (length == 0){
//当添加0个时,认为已结束加载到底
if (status==STATUS_INITIAL || status == STATUS_MORE){
footer.showNoMore();
status = STATUS_NOMORE;
}
}else {
//当Error或初始时。添加数据,如果有More则还原。
footer.showMore();
status = STATUS_MORE;
hasData = true;
}
}else{
if (hasNoMore){
footer.showNoMore();
status = STATUS_NOMORE;
}
}
isLoadingMore = false;
}
这个作者是根据每次填充数据的多少来判定加载状,如果你添加数据是0 则认为你加载完成了不需要去加载数据了
添加数据个数>0 则认为还有数据就会自动去调用footer.showMore 方法
public void showMore(){
Log.d("addData", "showMore: count:"+adapter.getItemCount());
flag = ShowMore;
if (adapter.getItemCount()>0)
adapter.notifyItemChanged(adapter.getItemCount()-1);
}
在这里会发送一个通知出去
发送通知后就会调用DefaultEventDelegate 类的OnBindView方法 你跟踪onBindView方法 会发它在
recyclerarrayAdapter OnBindView方法里被调用
public final void onBindViewHolder(BaseViewHolder holder, int position) {
...
if (footers.size()!=0 && i>=0){
footers.get(i).onBindView(holder.itemView);
return ;
}
....
}
也就是说每次发送通知会先调用 recyclerarrayAdapter 的onBindViewHolder方法,然后在调用DefaultEventDelegate
onBindView方法 通过这个方法去设置状态 因为加载更多 就会调用onMoreViewShowed方法
@Override
public void onBindView(View headerView) {
Log.d("addData", "onBindView: notifyed: flag"+flag);
headerView.post(new Runnable() {
@Override
public void run() {
switch (flag){
case ShowMore:
onMoreViewShowed();//
break;
case ShowNoMore:
if (!skipNoMore)onNoMoreViewShowed();skipNoMore = false;
break;
case ShowError:
if (!skipError) onErrorViewShowed();skipError = false;
break;
}
}
});
}
onMoreViewShowed 就会调用 onMoreListener.onMoreShow();
public void onMoreViewShowed() {
log("onMoreViewShowed");
if (!isLoadingMore&& onMoreListener !=null){
isLoadingMore = true;
onMoreListener.onMoreShow();
}
}
onMoreListener是什么东西呢?
RecyclerArrayAdapter.OnMoreListener onMoreListener;
也就是RecyclerArrayAdapter 类里面的 public void setMore(int res, RecyclerArrayAdapter.OnMoreListener listener) {
至此我们来分析一下刚才的bug,每次RecyclerArrayAdapter 调用add方法的 时候,
public void addAll(Collection<? extends T> collection) {
if (mEventDelegate!=null)mEventDelegate.addData(collection == null ? 0 : collection.size());
....
}
就会调用mEventDelegate.addData方法
addData方法 根据你添加的个数来判段加载的状态是0的时候则没有数据,
如果不是0的时候 就会调用加载更多的回调方法,footer.showMore
public void showMore(){
Log.d("addData", "showMore: count:"+adapter.getItemCount());
flag = ShowMore;
if (adapter.getItemCount()>0)
adapter.notifyItemChanged(adapter.getItemCount()-1);
}
这个方法会发送通知,调用了OnBindView方法继而去回调onLoadMore方法让你调分页的接口
你会不会发先一个问题,第一次刷新数据data>0 就会调加载的接口,data>0 继续调用接口,只要有数据就会不断的
调加载的接口,这个我测试了,不会发生的,加载的数据没有超出屏幕就会调用加载接口,超出屏幕后就不会调用了,
也就是说你每次翻页的时候,就会自动调用加载数据的接口,所以每次调用刷新接口,数据少的话,没有超出屏幕,就会
继续调用加载接口,直到数据超出屏幕。
如果你想实现第一次进去的时候,不要调用加载更多,你就改代码 addData(int length,boolean isLoading)
自己手动去控制它什么时候加载更多,什么时候不去加载
还有这个控件不能拓展刷新的界面和加载界面你可以使用superswiperefreshlayout 把swiperefreshlayout
给换掉哦
这个控件尽管不是很完美,但是毕竟比较好用,所以我宁可去改也不愿意去换它。
只要搞懂它的原理,一切都不是事情。