自己动手写一个简单的Android下拉刷新

概述

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章