public class RecycleViewScrollHelper extends RecyclerView.OnScrollListener {
private RecyclerView mRvScroll = null;
private OnScrollDirectionChangedListener mScrollDirectionChangedListener = null;
//滑動位置變動的監(jiān)聽事件
private OnScrollPositionChangedListener mScrollPositionChangedListener = null;
//是否同時檢測滑動到頂部及底部
private boolean mIsCheckTopBottomTogether = false;
//檢測滑動頂部/底部的優(yōu)先順序,默認先檢測滑動到底部
private boolean mIsCheckTopFirstBottomAfter = false;
//檢測底部滑動時是否檢測滿屏狀態(tài)
private boolean mIsCheckBottomFullRecycle = false;
//檢測頂部滑動時是否檢測滿屏狀態(tài)
private boolean mIsCheckTopFullRecycle = false;
//頂部滿屏檢測時允許的容差值
private int mTopOffsetFaultTolerance = 0;
//底部滿屏檢測時允許的容差值
private int mBottomOffsetFaultTolerance = 0;
private int mScrollDx = 0;
private int mScrollDy = 0;
/**
* recycleView的滑動監(jiān)聽事件,用于檢測是否滑動到頂部或者滑動到底部.
*
* @param listener {@link OnScrollPositionChangedListener}滑動位置變動監(jiān)聽事件
*/
public RecycleViewScrollHelper(OnScrollPositionChangedListener listener) {
mScrollPositionChangedListener = listener;
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView,
int newState) {
if (mScrollPositionChangedListener == null || recyclerView.getAdapter() == null || recyclerView.getChildCount() <= 0) {
return;
}
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearManager = (LinearLayoutManager) layoutManager;
int lastItemPosition = linearManager.findLastVisibleItemPosition();
int firstItemPosition = linearManager.findFirstVisibleItemPosition();
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//判斷頂部/底部檢測的優(yōu)先順序
if (!mIsCheckTopFirstBottomAfter) {
//先檢測底部
if (this.checkIfScrollToBottom(recyclerView, lastItemPosition, adapter.getItemCount())) {
//若檢測滑動到底部時,判斷是否需要同時檢測滑動到頂部
if (mIsCheckTopBottomTogether) {
//檢測是否滑動到頂部
this.checkIfScrollToTop(recyclerView, firstItemPosition);
//不管是否滑動到頂部,已經(jīng)觸發(fā)了滑動到底部,所以直接返回,否則會調(diào)用滑動到未知位置的
return;
} else {
//若不需要同時檢測,直接返回
return;
}
} else if (this.checkIfScrollToTop(recyclerView, firstItemPosition)) {
//當未檢測滑動到底部時,再檢測是否滑動到頂部
return;
}
} else {
//先檢測是否滑動到頂部
if (this.checkIfScrollToTop(recyclerView, firstItemPosition)) {
if (mIsCheckTopBottomTogether) {
//檢測是否滑動到底部
this.checkIfScrollToBottom(recyclerView, lastItemPosition, adapter.getItemCount());
return;
} else {
//若不需要同時檢測,直接返回
return;
}
} else if (this.checkIfScrollToBottom(recyclerView, lastItemPosition, adapter.getItemCount())) {
//當未檢測滑動到底部時,再檢測是否滑動到底部
return;
}
}
}
}
//其它任何情況
mScrollPositionChangedListener.onScrollToUnknown(false, false);
}
/**
* 檢測是否滑動到了頂部item并回調(diào)事件
*
* @param recyclerView
* @param firstItemPosition 第一個可見itemView的position
* @return
*/
private boolean checkIfScrollToTop(RecyclerView recyclerView, int firstItemPosition) {
if (firstItemPosition == 0) {
if (mIsCheckTopFullRecycle) {
int childCount = recyclerView.getChildCount();
View firstChild = recyclerView.getChildAt(0);
View lastChild = recyclerView.getChildAt(childCount - 1);
int top = firstChild.getTop();
int bottom = lastChild.getBottom();
//recycleView顯示itemView的有效區(qū)域的top坐標Y
int topEdge = recyclerView.getPaddingTop() - mTopOffsetFaultTolerance;
//recycleView顯示itemView的有效區(qū)域的bottom坐標Y
int bottomEdge = recyclerView.getHeight() - recyclerView.getPaddingBottom() - mBottomOffsetFaultTolerance;
//第一個view的頂部大于top邊界值,說明第一個view已經(jīng)完全顯示在頂部
//同時最后一個view的底部應(yīng)該小于bottom邊界值,說明最后一個view的底部已經(jīng)超出顯示范圍,部分或者完全移出了界面
if (top >= topEdge && bottom > bottomEdge) {
mScrollPositionChangedListener.onScrollToTop();
return true;
} else {
mScrollPositionChangedListener.onScrollToUnknown(true, false);
}
} else {
mScrollPositionChangedListener.onScrollToTop();
return true;
}
}
return false;
}
/**
* 檢測是否滑動到底部item并回調(diào)事件
*
* @param recyclerView
* @param lastItemPosition 最后一個可見itemView的position
* @param itemCount adapter的itemCount
* @return
*/
private boolean checkIfScrollToBottom(RecyclerView recyclerView, int lastItemPosition, int itemCount) {
if (lastItemPosition + 1 == itemCount) {
//是否進行滿屏的判斷處理
//未滿屏的情況下將永遠不會被回調(diào)滑動到低部或者頂部
if (mIsCheckBottomFullRecycle) {
int childCount = recyclerView.getChildCount();
//獲取最后一個childView
View lastChildView = recyclerView.getChildAt(childCount - 1);
//獲取第一個childView
View firstChildView = recyclerView.getChildAt(0);
int top = firstChildView.getTop();
int bottom = lastChildView.getBottom();
//recycleView顯示itemView的有效區(qū)域的bottom坐標Y
int bottomEdge = recyclerView.getHeight() - recyclerView.getPaddingBottom() + mBottomOffsetFaultTolerance;
//recycleView顯示itemView的有效區(qū)域的top坐標Y
int topEdge = recyclerView.getPaddingTop() + mTopOffsetFaultTolerance;
//第一個view的頂部小于top邊界值,說明第一個view已經(jīng)部分或者完全移出了界面
//最后一個view的底部小于bottom邊界值,說明最后一個view已經(jīng)完全顯示在界面
//若不處理這種情況,可能會存在recycleView高度足夠高時,itemView數(shù)量很少無法填充一屏,但是滑動到最后一項時依然會發(fā)生回調(diào)
//此時其實并不需要任何刷新操作的
if (bottom <= bottomEdge && top < topEdge) {
mScrollPositionChangedListener.onScrollToBottom();
return true;
} else {
mScrollPositionChangedListener.onScrollToUnknown(false, true);
}
} else {
mScrollPositionChangedListener.onScrollToBottom();
return true;
}
}
return false;
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (mScrollDirectionChangedListener != null) {
if (dx == 0 && dy == 0) {
mScrollDirectionChangedListener.onScrollDirectionChanged(0, 0);
} else if (dx == 0) {
boolean isUp = dy > 0;
boolean isBeenUp = mScrollDy > 0;
if (isUp != isBeenUp) {
mScrollDx = dx;
mScrollDy = dy;
mScrollDirectionChangedListener.onScrollDirectionChanged(dx, dy);
}
} else if (dy == 0) {
boolean isLeft = dx > 0;
boolean isBeenLeft = mScrollDx > 0;
if (isLeft != isBeenLeft) {
mScrollDx = dx;
mScrollDy = dy;
mScrollDirectionChangedListener.onScrollDirectionChanged(dx, dy);
}
}
}
}
//重置數(shù)據(jù)
private void reset() {
mScrollDx = 0;
mScrollDy = 0;
}
/**
* 關(guān)聯(lián)recycleView,當關(guān)聯(lián)新的recycleView時,會自動移除上一個關(guān)聯(lián)recycleView
*
* @param recyclerView
*/
public void attachToRecycleView(RecyclerView recyclerView) {
if (recyclerView != mRvScroll) {
unAttachToRecycleView();
mRvScroll = recyclerView;
if (recyclerView != null) {
recyclerView.addOnScrollListener(this);
}
}
}
/**
* 移除與recycleView的綁定
*/
public void unAttachToRecycleView() {
if (mRvScroll != null) {
mRvScroll.removeOnScrollListener(this);
}
this.reset();
}
/**
* 設(shè)置滑動方向改變時的回調(diào)接口
*
* @param listener
*/
public void setScrollDirectionChangedListener(OnScrollDirectionChangedListener listener) {
mScrollDirectionChangedListener = listener;
}
/**
* 設(shè)置頂部允許偏移的容差值,此值僅在允許檢測滿屏?xí)r有效,當{@link #setCheckIfItemViewFullRecycleViewForTop(boolean)}設(shè)置為true 或者{@link #setCheckIfItemViewFullRecycleViewForBottom(boolean)}設(shè)置為true 時有效.<br>
* 在檢測底部滑動時,對頂部的檢測會添加此容差值(更容易判斷當前第一項childView已超出recycleView的顯示范圍),用于協(xié)助判斷是否滑動到底部.
* 在檢測頂部滑動時,對頂部的檢測會添加此容差值(更容易判斷為滑動到了頂部)
*
* @param offset 容差值,此值必須為0或正數(shù)
*/
public void setTopOffsetFaultTolerance(@IntRange(from = 0) int offset) {
mTopOffsetFaultTolerance = offset;
}
/**
* 設(shè)置頂部允許偏移的容差值,此值僅在允許檢測滿屏?xí)r有效,當{@link #setCheckIfItemViewFullRecycleViewForTop(boolean)}設(shè)置為true 或者{@link #setCheckIfItemViewFullRecycleViewForBottom(boolean)}設(shè)置為true 時有效.<br>
* 在檢測底部滑動時,對底部的檢測會添加此容差值(更容易判斷當前最后一項childView已超出recycleView的顯示范圍),用于協(xié)助判斷是否滑動到頂部.
* 在檢測頂部滑動時,對底部的檢測會添加此容差值(更容易判斷為滑動到了底部)
*
* @param offset 容差值,此值必須為0或正數(shù)
*/
public void setBottomFaultTolerance(@IntRange(from = 0) int offset) {
mBottomOffsetFaultTolerance = offset;
}
/**
* 設(shè)置是否需要檢測recycleView是否為滿屏的itemView時才回調(diào)事件.<br>
* <p>
* 當RecycleView的childView數(shù)量很少時,有可能RecycleView已經(jīng)顯示出所有的itemView,此時不存在向上滑動的可能.<br>
* 若設(shè)置當前值為true時,只有在RecycleView無法完全顯示所有的itemView時,才會回調(diào)滑動到頂部的事件;否則將不處理;<br>
* 若設(shè)置為false則反之,不管任何時候只要滑動并頂部item顯示時都會回調(diào)滑動事件
*
* @param isNeedToCheck true為當檢測是否滿屏顯示;false不檢測,直接回調(diào)事件
*/
public void setCheckIfItemViewFullRecycleViewForTop(boolean isNeedToCheck) {
mIsCheckTopFullRecycle = isNeedToCheck;
}
/**
* 設(shè)置是否需要檢測recycleView是否為滿屏的itemView時才回調(diào)事件.<br>
* <p>
* 當RecycleView的childView數(shù)量很少時,有可能RecycleView已經(jīng)顯示o出所有的itemView,此時不存在向下滑動的可能.
* 若設(shè)置當前值為true時,只有在RecycleView無法完全顯示所有的itemView時,才會回調(diào)滑動到底部的事件;否則將不處理;
* 若設(shè)置為false則反之,不管任何時候只要滑動到底部都會回調(diào)滑動事件
*
* @param isNeedToCheck true為當檢測是否滿屏顯示;false不檢測,直接回調(diào)事件
*/
public void setCheckIfItemViewFullRecycleViewForBottom(boolean isNeedToCheck) {
mIsCheckBottomFullRecycle = isNeedToCheck;
}
/**
* 設(shè)置是否先檢測滑動到哪里.默認為false,先檢測滑動到底部
*
* @param isTopFirst true為先檢測滑動到頂部再檢測滑動到底部;false為先檢測滑動到底部再滑動到頂部
*/
public void setCheckScrollToTopFirstBottomAfter(boolean isTopFirst) {
mIsCheckTopFirstBottomAfter = isTopFirst;
}
/**
* 設(shè)置是否同時檢測滑動到頂部及底部,默認為false,先檢測到任何一個狀態(tài)都會直接返回,不會再繼續(xù)檢測其它狀態(tài)
*
* @param isCheckTogether true為兩種狀態(tài)都檢測,即使已經(jīng)檢測到其中某種狀態(tài)了.false為先檢測到任何一種狀態(tài)時將不再檢測另一種狀態(tài)
*/
public void setCheckScrollToTopBottomTogether(boolean isCheckTogether) {
mIsCheckTopBottomTogether = isCheckTogether;
}
/**
* 滑動位置改變監(jiān)聽事件,滑動到頂部/底部或者非以上兩個位置時
*/
public interface OnScrollPositionChangedListener {
/**
* 滑動到頂部的回調(diào)事件
*/
public void onScrollToTop();
/**
* 滑動到底部的回調(diào)事件
*/
public void onScrollToBottom();
/**
* 滑動到未知位置的回調(diào)事件
*
* @param isTopViewVisible 當前位置頂部第一個itemView是否可見,這里是指adapter中的最后一個itemView
* @param isBottomViewVisible 當前位置底部最后一個itemView是否可見,這里是指adapter中的最后一個itemView
*/
public void onScrollToUnknown(boolean isTopViewVisible, boolean isBottomViewVisible);
}
/**
* 滑動方向改變時監(jiān)聽事件
*/
public interface OnScrollDirectionChangedListener {
/**
* 滑動方向改變時監(jiān)聽事件,當兩個參數(shù)值都為0時,數(shù)據(jù)變動重新layout
*
* @param scrollVertical 豎直方向的滑動方向,向上<0,向下>0,不動(水平滑動時)=0
* @param scrollHorizontal 水平方向的滑動方向,向左<0,向右>0,不動(豎直滑動時)=0
*/
public void onScrollDirectionChanged(int scrollHorizontal, int scrollVertical);
}
}
工具類:判斷RecyclerView是否滑動到頭或底部
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
- 文/潘曉璐 我一進店門蛉签,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沥寥,你說我怎么就攤上這事碍舍。” “怎么了邑雅?”我有些...
- 文/不壞的土叔 我叫張陵片橡,是天一觀的道長。 經(jīng)常有香客問我淮野,道長捧书,這世上最難降的妖魔是什么? 我笑而不...
- 正文 為了忘掉前任录煤,我火速辦了婚禮鳄厌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妈踊。我一直安慰自己,他們只是感情好泪漂,可當我...
- 文/花漫 我一把揭開白布廊营。 她就那樣靜靜地躺著,像睡著了一般萝勤。 火紅的嫁衣襯著肌膚如雪露筒。 梳的紋絲不亂的頭發(fā)上,一...
- 文/蒼蘭香墨 我猛地睜開眼掌眠,長吁一口氣:“原來是場噩夢啊……” “哼蕾盯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蓝丙,我...
- 正文 年R本政府宣布,位于F島的核電站惩歉,受9級特大地震影響等脂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撑蚌,卻給世界環(huán)境...
- 文/蒙蒙 一上遥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧争涌,春花似錦粉楚、人聲如沸。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饮潦,卻和暖如春燃异,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背继蜡。 一陣腳步聲響...
推薦閱讀更多精彩內(nèi)容
- afinalAfinal是一個android的ioc雕凹,orm框架 https://github.com/yangf...
- 一般人看到經(jīng)濟學(xué)首先會想到金融枚抵, 總會聯(lián)想到炒股、外匯明场、黃金之類的字眼汽摹,其實炒股之類更需要的是學(xué)數(shù)學(xué)和計算機,而不...