4.Android RecyclerView完美支持下拉刷新和上拉加載(美團奔跑的小人)

請問:簡書怎么可以把代碼格式調(diào)整?我貼出來換格式了。你們直接去Github下載工程!

今天開始講RecycleView的系列教程。分割線,分組,局部刷新,動態(tài)添加,緩存原理,抖音效果,瀑布流。嵌套,動畫等等





框架重寫需要考慮的問題

1.是否支持下拉

2.是否支持上拉

3.自定義Header

4.自定義Footer

5.自定義動畫

原理介紹:

使用RecyclerView.OnScrollListener滾動的監(jiān)聽器來監(jiān)聽RecyclerView是否滑動到了底部,這樣我們就可以執(zhí)行添加底部動畫的操作。

邊界條件

根據(jù)原理我們能夠知道,做一個下拉刷新的動畫加載,我們需要做的有兩個動作:

1.如何判斷RecyclerView已經(jīng)滑動到底部。

2.如何添加RecyclerView的底部動畫。

重要的就是RecyclerView滾動監(jiān)聽

?RecyclerView.OnScrollListener?

mRecyclerView.canScrollVertically(1) 是否拉到底部

4.一些屬性的介紹

setWaveHeight、setHeaderHeight、setBottomHeight、setOverScrollHeight

setWaveHeight 設置頭部可拉伸的最大高度。

setHeaderHeight 頭部固定高度(在此高度上顯示刷新狀態(tài))

setBottomHeight 底部高度

setOverScrollHeight 設置最大的越界高度

setEnableRefresh、setEnableLoadmore

靈活的設置是否禁用上下拉。

setHeaderView(IHeaderView headerView)、setBottomView(IBottomView bottomView)

設置頭部/底部個性化刷新效果,頭部需要實現(xiàn)IHeaderView,底部需要實現(xiàn)IBottomView。

setEnableOverScroll

是否允許越界回彈。

總結(jié)步驟:

1.完成頭部添加和腳部添加

2.下拉刷新實現(xiàn)

3.同理,完成上拉加載

下拉刷新具體實現(xiàn):

需要用的接口:

1.自動下拉

2.可以停止下拉

3.可以動態(tài)設置下拉的ui

4.下拉完成回調(diào)

下拉核心東西

1.處理攔截事件:因為拖動和點擊事件有沖突


/****

? ? * 條目的點擊事件和滑動事件

? ? * @param ev

? ? * @return

? ? */

@Override

publicbooleandispatchTouchEvent(MotionEventev) {

switch(ev.getAction()) {

caseMotionEvent.ACTION_DOWN:

// 記錄手指按下的位置 ,之所以寫在dispatchTouchEvent那是因為如果我們處理了條目點擊事件,

// 那么就不會進入onTouchEvent里面,所以只能在這里獲取

mFingerDownY=(int)ev.getRawY();

break;

caseMotionEvent.ACTION_UP:

if(mCurrentDrag) {//拖動沒有超過一定距離就要還原

restoreRefreshView();

? ? ? ? ? ? ? ? }

break;

? ? ? ? }

returnsuper.dispatchTouchEvent(ev);

? ? }

2.處理滑動事件:


@Override

publicbooleanonTouchEvent(MotionEvente) {

switch(e.getAction()) {

caseMotionEvent.ACTION_MOVE:

// 如果是在最頂部才處理,否則不需要處理

if(canScrollUp()||mCurrentRefreshStatus==REFRESH_STATUS_REFRESHING) {

// 如果沒有到達最頂端,也就是說還可以向上滾動就什么都不處理

returnsuper.onTouchEvent(e);

? ? ? ? ? ? ? ? }

// 解決下拉刷新自動滾動問題

if(mCurrentDrag) {

scrollToPosition(0);

? ? ? ? ? ? ? ? }

// 獲取手指觸摸拖拽的距離

intdistanceY=(int) ((e.getRawY()-mFingerDownY)*mDragIndex);

// 如果是已經(jīng)到達頭部,并且不斷的向下拉,那么不斷的改變refreshView的marginTop的值

if(distanceY>0) {

intmarginTop=distanceY-mRefreshViewHeight;

setRefreshViewMarginTop(marginTop);

updateRefreshStatus(marginTop);

mCurrentDrag=true;

returnfalse;

? ? ? ? ? ? ? ? }

break;

? ? ? ? }

returnsuper.onTouchEvent(e);

? ? }

需要判斷是否到了頂部


/**

? ? * @return Whether it is possible for the child view of this layout to

? ? * scroll up. Override this if the child view is a custom view.

? ? * 判斷是不是滾動到了最頂部,這個是從SwipeRefreshLayout里面copy過來的源代碼

? ? */

publicbooleancanScrollUp() {

if(android.os.Build.VERSION.SDK_INT<14) {

returnViewCompat.canScrollVertically(this,-1)||this.getScrollY()>0;

}else{

returnViewCompat.canScrollVertically(this,-1);

? ? ? ? }

? ? }

滑動過程中要不斷修改recycleView的參數(shù)值


/**

? ? * 設置刷新View的marginTop

? ? */

publicvoidsetRefreshViewMarginTop(intmarginTop) {

MarginLayoutParamsparams=(MarginLayoutParams)mRefreshView.getLayoutParams();

if(marginTop<-mRefreshViewHeight+1) {

marginTop=-mRefreshViewHeight+1;

? ? ? ? }

params.topMargin=marginTop;

mRefreshView.setLayoutParams(params);

? ? }

重寫位置方法


@Override

protectedvoidonLayout(booleanchanged,intl,intt,intr,intb) {

super.onLayout(changed,l,t,r,b);

if(changed) {

if(mRefreshView!=null&&mRefreshViewHeight<=0) {

// 獲取頭部刷新View的高度

mRefreshViewHeight=mRefreshView.getMeasuredHeight();

if(mRefreshViewHeight>0) {

// 隱藏頭部刷新的View? marginTop? 多留出1px防止無法判斷是不是滾動到頭部問題

setRefreshViewMarginTop(-mRefreshViewHeight+1);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

1.定義接口:下拉,停止下拉,下拉回調(diào),下拉的view

* Description: 下拉刷新的輔助類為了匹配所有效果

*/

public abstract class RefreshViewCreator {

/**

? ? * 獲取下拉刷新的View

*

? ? * @param context 上下文

? ? * @param parent? RecyclerView

*/

? ? public abstract ViewgetRefreshView(Context context, ViewGroup parent);

? ? /**

? ? * 正在下拉

? ? * @param currentDragHeight? 當前拖動的高度

? ? * @param refreshViewHeight? 總的刷新高度

? ? * @param currentRefreshStatus 當前狀態(tài)

? ? */

? ? public abstract void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus);

? ? /**

? ? * 正在刷新中

? ? */

? ? public abstract void onRefreshing();

? ? /**

? ? * 停止刷新

? ? */

? ? public abstract void onStopRefresh();

}

2.核心東西。事件處理

/**

* Description: 下拉刷新的RecyclerView

*/

public class RefreshRecyclerViewextends WrapRecyclerView {

// 下拉刷新的輔助類

? ? private RefreshViewCreatormRefreshCreator;

? ? // 下拉刷新頭部的高度

? ? private int mRefreshViewHeight =0;

? ? // 下拉刷新的頭部View

? ? private ViewmRefreshView;

? ? // 手指按下的Y位置

? ? private int mFingerDownY;

? ? // 手指拖拽的阻力指數(shù)

? ? private float mDragIndex =0.35f;

? ? // 當前是否正在拖動

? ? private boolean mCurrentDrag =false;

? ? // 當前的狀態(tài)

? ? private int mCurrentRefreshStatus;

? ? // 默認狀態(tài)

? ? public int REFRESH_STATUS_NORMAL =0x0011;

? ? // 下拉刷新狀態(tài)

? ? public int REFRESH_STATUS_PULL_DOWN_REFRESH =0x0022;

? ? // 松開刷新狀態(tài)

? ? public int REFRESH_STATUS_LOOSEN_REFRESHING =0x0033;

? ? // 正在刷新狀態(tài)

? ? public int REFRESH_STATUS_REFRESHING =0x0033;

? ? public RefreshRecyclerView(Context context) {

super(context);

? ? }

public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

? ? }

public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

? ? }

// 先處理下拉刷新,同時考慮刷新列表的不同風格樣式,確保這個項目還是下一個項目都能用

? ? // 所以我們不能直接添加View,需要利用輔助類

? ? public void addRefreshViewCreator(RefreshViewCreator refreshCreator) {

this.mRefreshCreator = refreshCreator;

? ? ? ? addRefreshView();

? ? }

@Override

? ? public void setAdapter(Adapter adapter) {

super.setAdapter(adapter);

? ? ? ? addRefreshView();

? ? }

/****

? ? * 條目的點擊事件和滑動事件

? ? * @param ev

? ? * @return

? ? */

? ? @Override

? ? public boolean dispatchTouchEvent(MotionEvent ev) {

switch (ev.getAction()) {

case MotionEvent.ACTION_DOWN:

// 記錄手指按下的位置 ,之所以寫在dispatchTouchEvent那是因為如果我們處理了條目點擊事件,

? ? ? ? ? ? ? ? // 那么就不會進入onTouchEvent里面,所以只能在這里獲取

? ? ? ? ? ? ? ? mFingerDownY = (int) ev.getRawY();

break;

? ? ? ? ? ? case MotionEvent.ACTION_UP:

if (mCurrentDrag) {//拖動沒有超過一定距離就要還原

? ? ? ? ? ? ? ? ? ? restoreRefreshView();

? ? ? ? ? ? ? ? }

break;

? ? ? ? }

return super.dispatchTouchEvent(ev);

? ? }

/**

? ? * 重置當前刷新狀態(tài)狀態(tài)

? ? */

? ? private void restoreRefreshView() {

int currentTopMargin = ((MarginLayoutParams)mRefreshView.getLayoutParams()).topMargin;

? ? ? ? int finalTopMargin = -mRefreshViewHeight +1;

? ? ? ? if (mCurrentRefreshStatus ==REFRESH_STATUS_LOOSEN_REFRESHING) {

finalTopMargin =0;

? ? ? ? ? ? mCurrentRefreshStatus =REFRESH_STATUS_REFRESHING;

? ? ? ? ? ? if (mRefreshCreator !=null) {

mRefreshCreator.onRefreshing();

? ? ? ? ? ? }

if (mListener !=null) {

mListener.onRefresh();

? ? ? ? ? ? }

}

int distance = currentTopMargin - finalTopMargin;

? ? ? ? // 回彈到指定位置

? ? ? ? ValueAnimator animator = ObjectAnimator.ofFloat(currentTopMargin, finalTopMargin).setDuration(distance);

? ? ? ? animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

? ? ? ? ? ? public void onAnimationUpdate(ValueAnimator animation) {

float currentTopMargin = (float) animation.getAnimatedValue();

? ? ? ? ? ? ? ? setRefreshViewMarginTop((int) currentTopMargin);

? ? ? ? ? ? }

});

? ? ? ? animator.start();

? ? ? ? mCurrentDrag =false;

? ? }

@Override

? ? public boolean onTouchEvent(MotionEvent e) {

switch (e.getAction()) {

case MotionEvent.ACTION_MOVE:

// 如果是在最頂部才處理,否則不需要處理

? ? ? ? ? ? ? ? if (canScrollUp() ||mCurrentRefreshStatus ==REFRESH_STATUS_REFRESHING) {

// 如果沒有到達最頂端,也就是說還可以向上滾動就什么都不處理

? ? ? ? ? ? ? ? ? ? return super.onTouchEvent(e);

? ? ? ? ? ? ? ? }

// 解決下拉刷新自動滾動問題

? ? ? ? ? ? ? ? if (mCurrentDrag) {

scrollToPosition(0);

? ? ? ? ? ? ? ? }

// 獲取手指觸摸拖拽的距離

? ? ? ? ? ? ? ? int distanceY = (int) ((e.getRawY() -mFingerDownY) *mDragIndex);

? ? ? ? ? ? ? ? // 如果是已經(jīng)到達頭部,并且不斷的向下拉,那么不斷的改變refreshView的marginTop的值

? ? ? ? ? ? ? ? if (distanceY >0) {

int marginTop = distanceY -mRefreshViewHeight;

? ? ? ? ? ? ? ? ? ? setRefreshViewMarginTop(marginTop);

? ? ? ? ? ? ? ? ? ? updateRefreshStatus(marginTop);

? ? ? ? ? ? ? ? ? ? mCurrentDrag =true;

return false;

? ? ? ? ? ? ? ? }

break;

? ? ? ? }

return super.onTouchEvent(e);

? ? }

/**

? ? * 更新刷新的狀態(tài)

? ? */

? ? private void updateRefreshStatus(int marginTop) {

if (marginTop <= -mRefreshViewHeight) {

mCurrentRefreshStatus =REFRESH_STATUS_NORMAL;

? ? ? ? }else if (marginTop <0) {

mCurrentRefreshStatus =REFRESH_STATUS_PULL_DOWN_REFRESH;

? ? ? ? }else {

mCurrentRefreshStatus =REFRESH_STATUS_LOOSEN_REFRESHING;

? ? ? ? }

if (mRefreshCreator !=null) {

mRefreshCreator.onPull(marginTop, mRefreshViewHeight, mCurrentRefreshStatus);

? ? ? ? }

}

/**

? ? * 添加頭部的刷新View

*/

? ? private void addRefreshView() {

RecyclerView.Adapter adapter = getAdapter();

? ? ? ? if (adapter !=null &&mRefreshCreator !=null) {

// 添加頭部的刷新View

? ? ? ? ? ? View refreshView =mRefreshCreator.getRefreshView(getContext(), this);

? ? ? ? ? ? if (refreshView !=null) {

addHeaderView(refreshView);

? ? ? ? ? ? ? ? this.mRefreshView = refreshView;

? ? ? ? ? ? }

}

}

@Override

? ? protected void onLayout(boolean changed, int l, int t, int r, int b) {

super.onLayout(changed, l, t, r, b);

? ? ? ? if (changed) {

if (mRefreshView !=null &&mRefreshViewHeight <=0) {

// 獲取頭部刷新View的高度

? ? ? ? ? ? ? ? mRefreshViewHeight =mRefreshView.getMeasuredHeight();

? ? ? ? ? ? ? ? if (mRefreshViewHeight >0) {

// 隱藏頭部刷新的View? marginTop? 多留出1px防止無法判斷是不是滾動到頭部問題

? ? ? ? ? ? ? ? ? ? setRefreshViewMarginTop(-mRefreshViewHeight +1);

? ? ? ? ? ? ? ? }

}

}

}

/**

? ? * 設置刷新View的marginTop

*/

? ? public void setRefreshViewMarginTop(int marginTop) {

MarginLayoutParams params = (MarginLayoutParams)mRefreshView.getLayoutParams();

? ? ? ? if (marginTop < -mRefreshViewHeight +1) {

marginTop = -mRefreshViewHeight +1;

? ? ? ? }

params.topMargin = marginTop;

? ? ? ? mRefreshView.setLayoutParams(params);

? ? }

/**

? ? * @return Whether it is possible for the child view of this layout to

* scroll up. Override this if the child view is a custom view.

? ? * 判斷是不是滾動到了最頂部,這個是從SwipeRefreshLayout里面copy過來的源代碼

? ? */

? ? public boolean canScrollUp() {

if (android.os.Build.VERSION.SDK_INT <14) {

return ViewCompat.canScrollVertically(this, -1) ||this.getScrollY() >0;

? ? ? ? }else {

return ViewCompat.canScrollVertically(this, -1);

? ? ? ? }

}

/**

? ? * 停止刷新

? ? */

? ? public void onStopRefresh() {

mCurrentRefreshStatus =REFRESH_STATUS_NORMAL;

? ? ? ? restoreRefreshView();

? ? ? ? if (mRefreshCreator !=null) {

mRefreshCreator.onStopRefresh();

? ? ? ? }

}

// 處理刷新回調(diào)監(jiān)聽

? ? private OnRefreshListenermListener;

? ? public void setOnRefreshListener(OnRefreshListener listener) {

this.mListener = listener;

? ? }

public interface OnRefreshListener {

void onRefresh();

? ? }

}

3.調(diào)用


publicclassPullActivityextendsAppCompatActivity{

privateRefreshRecyclerViewmRecycleView;

privateLinearLayoutManagermLinearLayoutManager;//布局管理器

privateListmList;

@Override

protectedvoidonCreate(BundlesavedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_pull);

mList=newArrayList();

mRecycleView=findViewById(R.id.rv_list);

//初始化數(shù)據(jù)

initData(mList);

//創(chuàng)建布局管理器,垂直設置LinearLayoutManager.VERTICAL,水平設置LinearLayoutManager.HORIZONTAL

mLinearLayoutManager=newLinearLayoutManager(this,LinearLayoutManager.VERTICAL,false);

//創(chuàng)建適配器,將數(shù)據(jù)傳遞給適配器

//設置布局管理器

mRecycleView.setLayoutManager(mLinearLayoutManager);

MyRecycleViewAdapteradapter=newMyRecycleViewAdapter(mList);

mRecycleView.setAdapter(newWrapRecyclerAdapter(adapter));

defaultRefreshCreator=newDefaultRefreshCreator();

mRecycleView.addRefreshViewCreator(defaultRefreshCreator);

mRecycleView.setOnRefreshListener(newRefreshRecyclerView.OnRefreshListener() {

@Override

publicvoidonRefresh() {

Log.d("PullActivity","onRefresh");

? ? ? ? ? ? }

? ? ? ? });

handler.sendEmptyMessageDelayed(0,5000);

? ? }

Handlerhandler=newHandler() {

@Override

publicvoidhandleMessage(@NonNullMessagemsg) {

super.handleMessage(msg);

mRecycleView.onStopRefresh();//停止刷新

? ? ? ? }

? ? };

DefaultRefreshCreatordefaultRefreshCreator;

publicvoidinitData(Listlist) {

for(inti=1;i<=80;i++) {

list.add("第"+i+"條數(shù)據(jù)");

? ? ? ? }

? ? }

}

問題:

給你一個普通的頁面,你自己弄一個下拉刷新?怎么處理?

RecycleView是不是要搭配adapter一起?

不需要,recycleView和第三方的adataper是分開的,2個獨立的控件

不能滑動都問題

支持下拉刷新和上拉加載的RefreshLayout,自帶越界回彈效果,支持RecyclerView,AbsListView,ScrollView,WebView

Google官方推出了SwipeRefreshLayout和RecyclerView的共同使用,,為我們提供了更加便捷的列表下拉刷新功能,但是,并沒有給我們提供上拉加載功能

基于第三方:? ??SmartRefreshLayout

基于第三方控件TwinklingRefreshLayout

第一種方式:嵌套

使用finishRefreshing()方法結(jié)束刷新,finishLoadmore()方法結(jié)束加載更多。此處OnRefreshListener還有其它方法,可以選擇需要的來重寫。

如果你想進入到界面的時候主動調(diào)用下刷新,可以調(diào)用startRefresh()/startLoadmore()方法。(會自動執(zhí)行RefreshListenerAdapter監(jiān)聽事件中的onRefresh()/onLoadMore())

第二種方式:

方便實現(xiàn)個性化的 Header 和 Footer


demo地址:https://github.com/pengcaihua123456/shennandadao

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容