概述
一开始的时候尝试通过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,后续会完善各项功能