請問:簡書怎么可以把代碼格式調(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