easyrecyclerview 重新整理載入功能程式碼分析(填坑之旅)
阿新 • • 發佈:2019-01-05
想選一個重新整理載入 又可以新增各種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
給換掉哦
這個控制元件儘管不是很完美,但是畢竟比較好用,所以我寧可去改也不願意去換它。
只要搞懂它的原理,一切都不是事情。