自己動手寫一個簡單的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,後續會完善各項功能

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