概述
一開始的時候嘗試通過Android的事件分發機制來實現,但是child一旦消耗事件,那麼後續事件是不會傳給Parent的,只能重寫dispatchTouchEvent來攔截,這樣做相當於重寫了Android的事件分發機制,我自認水平是不夠的。隨後閱讀了Android官方刷新空間SwipeRefreshLayout,發現是使用的NestedScrolling機制,具體使用可以看鴻洋大神的這篇文章https://blog.csdn.net/lmj623565791/article/details/52204039,所以本篇文章就使用NestedScrolling機制來實現針對RecyclerView的下拉刷新功能,別的控件暫不支持。
實現思路
控件本身繼承自LinearLayout,有兩個child,分別是Header和Content,通過設置Header的TopMargin來控制Header的滑動效果。
實現NestedScrollingParent
維護兩個變量
private int mUnConsumedY = 0;
private int mHeaderShowHeight = 0;
mHeaderShowHeight表示當前Header顯示部分的高度,mUnConsumedY表示當前給RecyclerView消費掉的Y距離,通過這個變量來判斷RecyclerView是否滑到頂端。
實現onNestedPreScroll方法
通過這個方法實現在滑動之前判斷應該消費多少Y距離,只有兩種情況
- 向上滑動且mHeaderShowHeight大於0,這時候消費掉dy,不給child消費,同時更新mHeaderShowHeight
- 向下滑動且mUnConsumedY爲0,這時候消費dy。
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
if (dy > 0 && mHeaderShowHeight > 0) {
// header顯示的時候上滑
if (dy <= mHeaderShowHeight) {
mHeaderShowHeight -= dy;
consumed[1] = dy;
} else {
consumed[1] = mHeaderShowHeight;
mHeaderShowHeight = 0;
}
}
if (dy < 0 && mUnConsumedY == 0) {
consumed[1] = dy;
mHeaderShowHeight -= dy;
}
// 其他情況皆爲更新mUnConsumedY,在onNestedScroll方法中更新
Log.d("Debug", mHeaderShowHeight + " " + mUnConsumedY);
processHeaderShowHeight();
updateProgress(mHeaderShowHeight);
}
/**
* 處理mHeaderShowHeight,如果高於mHeaderHeight,加一個滑動的阻力
*/
private void processHeaderShowHeight() {
// 計算阻力作用後的mHeaderShowHeight
if (mHeaderShowHeight > mHeaderHeight) {
int extra = mHeaderShowHeight - mHeaderHeight;
// 阻力系數
float dragRatio = mHeaderHeight * 1.0f / mHeaderShowHeight;
mHeaderShowHeight = (int) (mHeaderHeight + extra * dragRatio);
}
// 通過mHeaderShowHeight來設置topMargin
setHeaderTopMarginWithShowHeight();
}
/**
* 根據mHeaderShowHeight更新當前進度
* @param mHeaderShowHeight
*/
private void updateProgress(int mHeaderShowHeight) {
if (mListener != null) {
mListener.onRefreshProgress(mHeaderShowHeight * 1.0f / mHeaderHeight);
}
}
其他的情況都是RecyclerView滑動,在onNestedScroll中更新mUnConsumedY
實現onNestedScroll方法
這個方法更新mUnConsumedY
/**
* 在子view滑動後通過消耗和未消耗的,來計算子view是否滑動到最頂端
* @param target
* @param dxConsumed
* @param dyConsumed
* @param dxUnconsumed
* @param dyUnconsumed
*/
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
mUnConsumedY += dyConsumed;
mUnConsumedY = Math.max(mUnConsumedY, 0);
}
實現onStopNestedScroll方法
這個方法裏判斷當前的狀態,如果不是正在刷新或者釋放刷新狀態,都隱藏Header,否則進入刷新狀態,判斷是否釋放刷新狀態的閾值通過回調獲得,回調下面來說
@Override
public void onStopNestedScroll(View child) {
super.onStopNestedScroll(child);
if (mHeaderShowHeight > 0) {
float changeStateRatio = mListener == null? 1.0f : mListener.getChangeStateRatio();
if (mHeaderShowHeight * 1.0f / mHeaderHeight > changeStateRatio) {
// 開始刷新
startRefreshing();
} else {
stopRefreshing();
}
}
}
實現onNestedPreFling
由於Fling操作會導致mUnConsumedY不能正常更新,所以重寫這個方法返回true來禁止child實現這個操作
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return true;
}
定義刷新回調
public interface EasyRefreshListener {
void onRefreshing();
// 根據progress改變內容
void onRefreshProgress(float progress);
// 改變下拉狀態的閾值
float getChangeStateRatio();
}
結語
以上就是大致思路,具體的代碼已經上傳到Github,後續會完善各項功能