概述
一开始的时候尝试通过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的滑动效果。
维护两个变量
1 2
| private int mUnConsumedY = 0; private int mHeaderShowHeight = 0;
|
mHeaderShowHeight表示当前Header显示部分的高度,mUnConsumedY表示当前给RecyclerView消费掉的Y距离,通过这个变量来判断RecyclerView是否滑到顶端。
通过这个方法实现在滑动之前判断应该消费多少Y距离,只有两种情况
- 向上滑动且mHeaderShowHeight大于0,这时候消费掉dy,不给child消费,同时更新mHeaderShowHeight
- 向下滑动且mUnConsumedY为0,这时候消费dy。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { if (dy > 0 && mHeaderShowHeight > 0) { if (dy <= mHeaderShowHeight) { mHeaderShowHeight -= dy; consumed[1] = dy; } else { consumed[1] = mHeaderShowHeight; mHeaderShowHeight = 0; } } if (dy < 0 && mUnConsumedY == 0) { consumed[1] = dy; mHeaderShowHeight -= dy; } Log.d("Debug", mHeaderShowHeight + " " + mUnConsumedY); processHeaderShowHeight(); updateProgress(mHeaderShowHeight); }
private void processHeaderShowHeight() { if (mHeaderShowHeight > mHeaderHeight) { int extra = mHeaderShowHeight - mHeaderHeight; float dragRatio = mHeaderHeight * 1.0f / mHeaderShowHeight; mHeaderShowHeight = (int) (mHeaderHeight + extra * dragRatio); } setHeaderTopMarginWithShowHeight(); }
private void updateProgress(int mHeaderShowHeight) { if (mListener != null) { mListener.onRefreshProgress(mHeaderShowHeight * 1.0f / mHeaderHeight); } }
|
其他的情况都是RecyclerView滑动,在onNestedScroll中更新mUnConsumedY
这个方法更新mUnConsumedY
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@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); }
|
这个方法里判断当前的状态,如果不是正在刷新或者释放刷新状态,都隐藏Header,否则进入刷新状态,判断是否释放刷新状态的阈值通过回调获得,回调下面来说
1 2 3 4 5 6 7 8 9 10 11 12 13
| @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实现这个操作
1 2 3 4
| @Override public boolean onNestedPreFling(View target, float velocityX, float velocityY) { return true; }
|
定义刷新回调
1 2 3 4 5 6 7 8 9 10
| public interface EasyRefreshListener {
void onRefreshing();
void onRefreshProgress(float progress);
float getChangeStateRatio(); }
|
结语
以上就是大致思路,具体的代码已经上传到Github,后续会完善各项功能