RecyclerView更全解析之 - 打造通用的下拉刷新上拉加載

1.概述


這期我們?cè)谏弦黄诘?a target="_blank" rel="nofollow">RecyclerView更全解析之 - 為它優(yōu)雅的添加頭部和底部的基礎(chǔ)上再去增加功能,我相信我們?cè)谡嬲膶?shí)踐開(kāi)發(fā)過(guò)程中肯定少不了下拉刷新和上拉加載。
  我們需要思考一個(gè)問(wèn)題上拉刷新下拉加載風(fēng)格各式各樣,淘寶和京東的列表刷新樣式就肯定不一樣,我們?cè)趺礃幼龅桨姹镜臅r(shí)候可以快速的更改樣式。有時(shí)還需要顯示正在加載數(shù)據(jù)或者無(wú)數(shù)據(jù),比如篩選的時(shí)候有可能會(huì)出現(xiàn)沒(méi)有數(shù)據(jù)的情況會(huì)顯示無(wú)數(shù)據(jù)頁(yè)面,怎么快速做到?當(dāng)然如果你對(duì)系統(tǒng)架構(gòu)比較了解那就非常簡(jiǎn)單了,又或者是你對(duì)面向?qū)ο蟮牧蠡驹瓌t比較熟悉也行。

相關(guān)文章:
  
  RecyclerView更全解析之 - 基本使用和分割線解析
  
  RecyclerView更全解析之 - 打造通用的萬(wàn)能Adapter
  
  RecyclerView更全解析之 - 為它優(yōu)雅的添加頭部和底部
  
  RecyclerView更全解析之 - 打造通用的下拉刷新上拉加載
  
  RecyclerView更全解析之 - 仿支付寶側(cè)滑刪除和拖動(dòng)排序
  
  
  

這里寫圖片描述

2.基本思路


我們?cè)趯戫?xiàng)目或是搭建架構(gòu)的時(shí)候需要考慮最多的是擴(kuò)展,而不是先把所有的功能寫在一起或是全部寫好,或者說(shuō)代碼過(guò)度設(shè)計(jì)本來(lái)很簡(jiǎn)單的東西你非得跟人解釋這怎么怎么的,很忌諱。
  肯定是希望目前寫好的東西,以后如果出現(xiàn)什么問(wèn)題或者添加新的功能都不需要去修改我們已經(jīng)寫好的代碼,而是在原來(lái)的基礎(chǔ)上利用面向?qū)ο蟮乃枷肴U(kuò)展無(wú)論你是繼承也好還是實(shí)現(xiàn)也好都行,就不會(huì)出現(xiàn)需求改變的時(shí)候我們的代碼就改成了別人口中說(shuō)的改成了......
  
  本著這個(gè)原則我們大致的思想就是:

  • 先處理下拉刷新,同時(shí)考慮刷新列表的不同風(fēng)格樣式,確保這個(gè)項(xiàng)目還是下一個(gè)項(xiàng)目都能用
  • 再處理上拉加載更多,只需去繼承寫好的下拉刷新控件即可
  • 可以適當(dāng)?shù)脑黾右恍┗竟δ?,如正在加載列表樣式或者說(shuō)是無(wú)頁(yè)面數(shù)據(jù)樣式
  • 封裝通用默認(rèn)的樣式,封裝好整個(gè)項(xiàng)目的通用樣式,如果下次需要修改擴(kuò)展即可
  • 最后思考一下我們這樣去寫合不合理,給自己的同事用用自己和他們都做一下測(cè)評(píng)和修改

3.基本實(shí)現(xiàn)


3.1 下拉刷新
  先處理下拉刷新,同時(shí)考慮刷新列表的不同風(fēng)格樣式,確保這個(gè)項(xiàng)目還是下一個(gè)項(xiàng)目都能用。這里我們肯定是繼承上一期的可以直接添加頭部和底部的WrapRecyclerView,為了確保實(shí)現(xiàn)不同的樣式,需要一個(gè)額外的輔助類:

/**
 * Created by Darren on 2017/1/3.
 * Email: 240336124@qq.com
 * Description: 下拉刷新的輔助類為了匹配所有效果
 */

public abstract class RefreshViewCreator {

    /**
     * 獲取下拉刷新的View
     *
     * @param context 上下文
     * @param parent  RecyclerView
     */
    public abstract View getRefreshView(Context context, ViewGroup parent);

    /**
     * 正在下拉
     * @param currentDragHeight   當(dāng)前拖動(dòng)的高度
     * @param refreshViewHeight  總的刷新高度
     * @param currentRefreshStatus 當(dāng)前狀態(tài)
     */
    public abstract void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus);

    /**
     * 正在刷新中
     */
    public abstract void onRefreshing();

    /**
     * 停止刷新
     */
    public abstract void onStopRefresh();
}

/**
 * Created by Darren on 2017/1/3.
 * Email: 240336124@qq.com
 * Description: 下拉刷新的RecyclerView
 */
public class RefreshRecyclerView extends WrapRecyclerView {
    // 下拉刷新的輔助類
    private RefreshViewCreator mRefreshCreator;
    // 下拉刷新頭部的高度
    private int mRefreshViewHeight = 0;
    // 下拉刷新的頭部View
    private View mRefreshView;
    // 手指按下的Y位置
    private int mFingerDownY;
    // 手指拖拽的阻力指數(shù)
    private float mDragIndex = 0.35f;
    // 當(dāng)前是否正在拖動(dòng)
    private boolean mCurrentDrag = false;
    // 當(dāng)前的狀態(tài)
    private int mCurrentRefreshStatus;
    // 默認(rèn)狀態(tài)
    public int REFRESH_STATUS_NORMAL = 0x0011;
    // 下拉刷新?tīng)顟B(tài)
    public int REFRESH_STATUS_PULL_DOWN_REFRESH = 0x0022;
    // 松開(kāi)刷新?tīng)顟B(tài)
    public int REFRESH_STATUS_LOOSEN_REFRESHING = 0x0033;
    // 正在刷新?tīng)顟B(tài)
    public int REFRESH_STATUS_REFRESHING = 0x0033;

    public RefreshRecyclerView(Context context) {
        super(context);
    }

    public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // 先處理下拉刷新,同時(shí)考慮刷新列表的不同風(fēng)格樣式,確保這個(gè)項(xiàng)目還是下一個(gè)項(xiàng)目都能用
    // 所以我們不能直接添加View,需要利用輔助類
    public void addRefreshViewCreator(RefreshViewCreator refreshCreator) {
        this.mRefreshCreator = refreshCreator;
        addRefreshView();
    }

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);
        addRefreshView();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄手指按下的位置 ,之所以寫在dispatchTouchEvent那是因?yàn)槿绻覀兲幚砹藯l目點(diǎn)擊事件,
                // 那么就不會(huì)進(jìn)入onTouchEvent里面,所以只能在這里獲取
                mFingerDownY = (int) ev.getRawY();
                break;

            case MotionEvent.ACTION_UP:
                if (mCurrentDrag) {
                    restoreRefreshView();
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 重置當(dāng)前刷新?tīng)顟B(tài)狀態(tài)
     */
    private void restoreRefreshView() {
        int currentTopMargin = ((MarginLayoutParams) mRefreshView.getLayoutParams()).topMargin;
        int finalTopMargin = -mRefreshViewHeight + 1;
        if (mCurrentRefreshStatus == REFRESH_STATUS_LOOSEN_REFRESHING) {
            finalTopMargin = 0;
            mCurrentRefreshStatus = REFRESH_STATUS_REFRESHING;
            if (mRefreshCreator != null) {
                mRefreshCreator.onRefreshing();
            }
            if (mListener != null) {
                mListener.onRefresh();
            }
        }

        int distance = currentTopMargin - finalTopMargin;

        // 回彈到指定位置
        ValueAnimator animator = ObjectAnimator.ofFloat(currentTopMargin, finalTopMargin).setDuration(distance);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float currentTopMargin = (float) animation.getAnimatedValue();
                setRefreshViewMarginTop((int) currentTopMargin);
            }
        });
        animator.start();
        mCurrentDrag = false;
    }


    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                // 如果是在最頂部才處理,否則不需要處理
                if (canScrollUp() || mCurrentRefreshStatus == REFRESH_STATUS_REFRESHING) {
                    // 如果沒(méi)有到達(dá)最頂端,也就是說(shuō)還可以向上滾動(dòng)就什么都不處理
                    return super.onTouchEvent(e);
                }

                // 解決下拉刷新自動(dòng)滾動(dòng)問(wèn)題
                if (mCurrentDrag) {
                    scrollToPosition(0);
                }

                // 獲取手指觸摸拖拽的距離
                int distanceY = (int) ((e.getRawY() - mFingerDownY) * mDragIndex);
                // 如果是已經(jīng)到達(dá)頭部,并且不斷的向下拉,那么不斷的改變r(jià)efreshView的marginTop的值
                if (distanceY > 0) {
                    int marginTop = distanceY - mRefreshViewHeight;
                    setRefreshViewMarginTop(marginTop);
                    updateRefreshStatus(marginTop);
                    mCurrentDrag = true;
                    return false;
                }
                break;
        }

        return super.onTouchEvent(e);
    }

    /**
     * 更新刷新的狀態(tài)
     */
    private void updateRefreshStatus(int marginTop) {
        if (marginTop <= -mRefreshViewHeight) {
            mCurrentRefreshStatus = REFRESH_STATUS_NORMAL;
        } else if (marginTop < 0) {
            mCurrentRefreshStatus = REFRESH_STATUS_PULL_DOWN_REFRESH;
        } else {
            mCurrentRefreshStatus = REFRESH_STATUS_LOOSEN_REFRESHING;
        }

        if (mRefreshCreator != null) {
            mRefreshCreator.onPull(marginTop, mRefreshViewHeight, mCurrentRefreshStatus);
        }
    }

    /**
     * 添加頭部的刷新View
     */
    private void addRefreshView() {
        RecyclerView.Adapter adapter = getAdapter();
        if (adapter != null && mRefreshCreator != null) {
            // 添加頭部的刷新View
            View refreshView = mRefreshCreator.getRefreshView(getContext(), this);
            if (refreshView != null) {
                addHeaderView(refreshView);
                this.mRefreshView = refreshView;
            }
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (changed) {
            if (mRefreshView != null && mRefreshViewHeight <= 0) {
                // 獲取頭部刷新View的高度
                mRefreshViewHeight = mRefreshView.getMeasuredHeight();
                if (mRefreshViewHeight > 0) {
                    // 隱藏頭部刷新的View  marginTop  多留出1px防止無(wú)法判斷是不是滾動(dòng)到頭部問(wèn)題
                    setRefreshViewMarginTop(-mRefreshViewHeight + 1);
                }
            }
        }
    }

    /**
     * 設(shè)置刷新View的marginTop
     */
    public void setRefreshViewMarginTop(int marginTop) {
        MarginLayoutParams params = (MarginLayoutParams) mRefreshView.getLayoutParams();
        if (marginTop < -mRefreshViewHeight + 1) {
            marginTop = -mRefreshViewHeight + 1;
        }
        params.topMargin = marginTop;
        mRefreshView.setLayoutParams(params);
    }


    /**
     * @return Whether it is possible for the child view of this layout to
     * scroll up. Override this if the child view is a custom view.
     * 判斷是不是滾動(dòng)到了最頂部,這個(gè)是從SwipeRefreshLayout里面copy過(guò)來(lái)的源代碼
     */
    public boolean canScrollUp() {
        if (android.os.Build.VERSION.SDK_INT < 14) {
            return ViewCompat.canScrollVertically(this, -1) || this.getScrollY() > 0;
        } else {
            return ViewCompat.canScrollVertically(this, -1);
        }
    }

    /**
     * 停止刷新
     */
    public void onStopRefresh() {
        mCurrentRefreshStatus = REFRESH_STATUS_NORMAL;
        restoreRefreshView();
        if (mRefreshCreator != null) {
            mRefreshCreator.onStopRefresh();
        }
    }

    // 處理刷新回調(diào)監(jiān)聽(tīng)
    private OnRefreshListener mListener;

    public void setOnRefreshListener(OnRefreshListener listener) {
        this.mListener = listener;
    }

    public interface OnRefreshListener {
        void onRefresh();
    }
}

我們來(lái)寫一個(gè)默認(rèn)的下拉刷新效果測(cè)試一下,這個(gè)gif錄制軟件的效果不是特別給力

/**
 * Created by Darren on 2017/1/3.
 * Email: 240336124@qq.com
 * Description: 默認(rèn)樣式的頭部刷新
 *              如淘寶、京東、不同的樣式可以自己去實(shí)現(xiàn)
 */

public class DefaultRefreshCreator extends RefreshViewCreator {
    // 加載數(shù)據(jù)的ImageView
    private View mRefreshIv;

    @Override
    public View getRefreshView(Context context, ViewGroup parent) {
        View refreshView = LayoutInflater.from(context).inflate(R.layout.layout_refresh_header_view, parent, false);
        mRefreshIv = refreshView.findViewById(R.id.refresh_iv);
        return refreshView;
    }

    @Override
    public void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus) {
        float rotate = ((float) currentDragHeight) / refreshViewHeight;
        // 不斷下拉的過(guò)程中不斷的旋轉(zhuǎn)圖片
        mRefreshIv.setRotation(rotate * 360);
    }

    @Override
    public void onRefreshing() {
        // 刷新的時(shí)候不斷旋轉(zhuǎn)
        RotateAnimation animation = new RotateAnimation(0, 720,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setRepeatCount(-1);
        animation.setDuration(1000);
        mRefreshIv.startAnimation(animation);
    }

    @Override
    public void onStopRefresh() {
        // 停止加載的時(shí)候清除動(dòng)畫
        mRefreshIv.setRotation(0);
        mRefreshIv.clearAnimation();
    }
}

這里寫圖片描述

3.2 處理上拉加載更多
  再處理上拉加載更多,只需去繼承寫好的下拉刷新控件即可。我們的確可以在原來(lái)的這個(gè)下拉刷新的控件中去寫,但是有幾個(gè)問(wèn)題都寫到一堆出了問(wèn)題找誰(shuí)?別人怎么看代碼?如果該需求只要下拉刷新呢?說(shuō)好的擴(kuò)展。所以我們新寫一個(gè)控件繼承已經(jīng)寫好的下拉刷新控件每個(gè)類負(fù)責(zé)單獨(dú)的事情

 /**
 * Created by Darren on 2017/1/3.
 * Email: 240336124@qq.com
 * Description: 上拉加載更多的輔助類為了匹配所有效果
 */

public abstract class LoadViewCreator {

    /**
     * 獲取上拉加載更多的View
     *
     * @param context 上下文
     * @param parent  RecyclerView
     */
    public abstract View getLoadView(Context context, ViewGroup parent);

    /**
     * 正在上拉
     *
     * @param currentDragHeight    當(dāng)前拖動(dòng)的高度
     * @param loadViewHeight    總的加載高度
     * @param currentLoadStatus 當(dāng)前狀態(tài)
     */
    public abstract void onPull(int currentDragHeight, int loadViewHeight, int currentLoadStatus);

    /**
     * 正在加載中
     */
    public abstract void onLoading();

    /**
     * 停止加載
     */
    public abstract void onStopLoad();
}

/**
 * Created by Darren on 2017/1/3.
 * Email: 240336124@qq.com
 * Description: 下拉刷新上拉加載更多的RecyclerView
 */
public class LoadRefreshRecyclerView extends RefreshRecyclerView {
    // 上拉加載更多的輔助類
    private LoadViewCreator mLoadCreator;
    // 上拉加載更多頭部的高度
    private int mLoadViewHeight = 0;
    // 上拉加載更多的頭部View
    private View mLoadView;
    // 手指按下的Y位置
    private int mFingerDownY;
    // 當(dāng)前是否正在拖動(dòng)
    private boolean mCurrentDrag = false;
    // 當(dāng)前的狀態(tài)
    private int mCurrentLoadStatus;
    // 默認(rèn)狀態(tài)
    public int LOAD_STATUS_NORMAL = 0x0011;
    // 上拉加載更多狀態(tài)
    public static int LOAD_STATUS_PULL_DOWN_REFRESH = 0x0022;
    // 松開(kāi)加載更多狀態(tài)
    public static int LOAD_STATUS_LOOSEN_LOADING = 0x0033;
    // 正在加載更多狀態(tài)
    public int LOAD_STATUS_LOADING = 0x0044;

    public LoadRefreshRecyclerView(Context context) {
        super(context);
    }

    public LoadRefreshRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public LoadRefreshRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // 先處理上拉加載更多,同時(shí)考慮加載列表的不同風(fēng)格樣式,確保這個(gè)項(xiàng)目還是下一個(gè)項(xiàng)目都能用
    // 所以我們不能直接添加View,需要利用輔助類
    public void addLoadViewCreator(LoadViewCreator loadCreator) {
        this.mLoadCreator = loadCreator;
        addRefreshView();
    }

    @Override
    public void setAdapter(Adapter adapter) {
        super.setAdapter(adapter);
        addRefreshView();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 記錄手指按下的位置 ,之所以寫在dispatchTouchEvent那是因?yàn)槿绻覀兲幚砹藯l目點(diǎn)擊事件,
                // 那么就不會(huì)進(jìn)入onTouchEvent里面,所以只能在這里獲取
                mFingerDownY = (int) ev.getRawY();
                break;

            case MotionEvent.ACTION_UP:
                if (mCurrentDrag) {
                    restoreLoadView();
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 重置當(dāng)前加載更多狀態(tài)
     */
    private void restoreLoadView() {
        int currentBottomMargin = ((MarginLayoutParams) mLoadView.getLayoutParams()).bottomMargin;
        int finalBottomMargin = 0;
        if (mCurrentLoadStatus == LOAD_STATUS_LOOSEN_LOADING) {
            mCurrentLoadStatus = LOAD_STATUS_LOADING;
            if (mLoadCreator != null) {
                mLoadCreator.onLoading();
            }
            if (mListener != null) {
                mListener.onLoad();
            }
        }

        int distance = currentBottomMargin - finalBottomMargin;

        // 回彈到指定位置
        ValueAnimator animator = ObjectAnimator.ofFloat(currentBottomMargin, finalBottomMargin).setDuration(distance);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float currentTopMargin = (float) animation.getAnimatedValue();
                setLoadViewMarginBottom((int) currentTopMargin);
            }
        });
        animator.start();
        mCurrentDrag = false;
    }


    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                // 如果是在最底部才處理,否則不需要處理
                if (canScrollDown() || mCurrentLoadStatus == LOAD_STATUS_LOADING) {
                    // 如果沒(méi)有到達(dá)最頂端,也就是說(shuō)還可以向上滾動(dòng)就什么都不處理
                    return super.onTouchEvent(e);
                }

                if (mLoadCreator != null) {
                    mLoadViewHeight = mLoadView.getMeasuredHeight();
                }
                // 解決上拉加載更多自動(dòng)滾動(dòng)問(wèn)題
                if (mCurrentDrag) {
                    scrollToPosition(getAdapter().getItemCount() - 1);
                }

                // 獲取手指觸摸拖拽的距離
                int distanceY = (int) ((e.getRawY() - mFingerDownY) * mDragIndex);
                // 如果是已經(jīng)到達(dá)頭部,并且不斷的向下拉,那么不斷的改變r(jià)efreshView的marginTop的值
                if (distanceY < 0) {
                    setLoadViewMarginBottom(-distanceY);
                    updateLoadStatus(-distanceY);
                    mCurrentDrag = true;
                    return true;
                }
                break;
        }

        return super.onTouchEvent(e);
    }

    /**
     * 更新加載的狀態(tài)
     */
    private void updateLoadStatus(int distanceY) {
        if (distanceY <= 0) {
            mCurrentLoadStatus = LOAD_STATUS_NORMAL;
        } else if (distanceY < mLoadViewHeight) {
            mCurrentLoadStatus = LOAD_STATUS_PULL_DOWN_REFRESH;
        } else {
            mCurrentLoadStatus = LOAD_STATUS_LOOSEN_LOADING;
        }

        if (mLoadCreator != null) {
            mLoadCreator.onPull(distanceY, mLoadViewHeight, mCurrentLoadStatus);
        }
    }

    /**
     * 添加底部加載更多View
     */
    private void addRefreshView() {
        Adapter adapter = getAdapter();
        if (adapter != null && mLoadCreator != null) {
            // 添加底部加載更多View
            View loadView = mLoadCreator.getLoadView(getContext(), this);
            if (loadView != null) {
                addFooterView(loadView);
                this.mLoadView = loadView;
            }
        }
    }

    /**
     * 設(shè)置加載View的marginBottom
     */
    public void setLoadViewMarginBottom(int marginBottom) {
        MarginLayoutParams params = (MarginLayoutParams) mLoadView.getLayoutParams();
        if (marginBottom < 0) {
            marginBottom = 0;
        }
        params.bottomMargin = marginBottom;
        mLoadView.setLayoutParams(params);
    }


    /**
     * @return Whether it is possible for the child view of this layout to
     * scroll up. Override this if the child view is a custom view.
     * 判斷是不是滾動(dòng)到了最頂部,這個(gè)是從SwipeRefreshLayout里面copy過(guò)來(lái)的源代碼
     */
    public boolean canScrollDown() {
        return ViewCompat.canScrollVertically(this, 1);
    }

    /**
     * 停止加載更多
     */
    public void onStopLoad() {
        mCurrentLoadStatus = LOAD_STATUS_NORMAL;
        restoreLoadView();
        if (mLoadCreator != null) {
            mLoadCreator.onStopLoad();
        }
    }

    // 處理加載更多回調(diào)監(jiān)聽(tīng)
    private OnLoadMoreListener mListener;

    public void setOnLoadMoreListener(OnLoadMoreListener listener) {
        this.mListener = listener;
    }

    public interface OnLoadMoreListener {
        void onLoad();
    }
}
這里寫圖片描述

3.3 增加一些基本通用功能

最后我們?cè)谶@個(gè)基礎(chǔ)在增加一些基本的功能,如正在加載數(shù)據(jù)的頁(yè)面,或者數(shù)據(jù)是空的頁(yè)面,所以決定找一層最合適的方法去改,那就是我們上一期的WrapRecyclerView的基礎(chǔ)上去改,因?yàn)槟鞘俏覀傾dapter密切聯(lián)系的一層。

/**
 * Created by Darren on 2016/12/29.
 * Email: 240336124@qq.com
 * Description: 可以添加頭部和底部的RecyclerView
 */
public class WrapRecyclerView extends RecyclerView {
    // 增加一些通用功能
    // 空列表數(shù)據(jù)應(yīng)該顯示的空View
    // 正在加載數(shù)據(jù)頁(yè)面,也就是正在獲取后臺(tái)接口頁(yè)面
    private View mEmptyView, mLoadingView;

    // 省略...上一期已有代碼

    private AdapterDataObserver mDataObserver = new AdapterDataObserver() {
        @Override
        public void onChanged() {
            if (mAdapter == null) return;
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged沒(méi)效果
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyDataSetChanged();

            dataChanged();
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            if (mAdapter == null) return;
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyDataSetChanged沒(méi)效果
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemRemoved(positionStart);
            dataChanged();
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            if (mAdapter == null) return;
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemMoved沒(méi)效果
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemMoved(fromPosition, toPosition);
            dataChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount) {
            if (mAdapter == null) return;
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged沒(méi)效果
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemChanged(positionStart);
            dataChanged();
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            if (mAdapter == null) return;
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemChanged沒(méi)效果
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemChanged(positionStart, payload);
            dataChanged();
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            if (mAdapter == null) return;
            // 觀察者  列表Adapter更新 包裹的也需要更新不然列表的notifyItemInserted沒(méi)效果
            if (mWrapRecyclerAdapter != mAdapter)
                mWrapRecyclerAdapter.notifyItemInserted(positionStart);
            dataChanged();
        }
    };

    /**
     * 添加一個(gè)空列表數(shù)據(jù)頁(yè)面
     */
    public void addEmptyView(View emptyView) {
        this.mEmptyView = emptyView;
    }

    /**
     * 添加一個(gè)正在加載數(shù)據(jù)的頁(yè)面
     */
    public void addLoadingView(View loadingView) {
        this.mLoadingView = loadingView;
    }

    /**
     * Adapter數(shù)據(jù)改變的方法
     */
    private void dataChanged() {
        if (mAdapter.getItemCount() == 0) {
            // 沒(méi)有數(shù)據(jù)
            if (mEmptyView != null) {
                mEmptyView.setVisibility(VISIBLE);
            } else {
                mEmptyView.setVisibility(GONE);
            }
        }
    }
    // 省略...上一期已有代碼
}

上一期的代碼就已經(jīng)省略了RecyclerView更全解析之 - 為它優(yōu)雅的添加頭部和底部,到目前應(yīng)該所有的這些列表刷新和加載樣式都可以實(shí)現(xiàn),具體的一些要求可以自己修改修改。我這里就不在把它使用到具體的項(xiàng)目中了,我自己也用到了自己的項(xiàng)目中,之所以之前沒(méi)寫這一期的博客是因?yàn)樵谑褂玫倪^(guò)程中出現(xiàn)了一些Bug,所以才等到這個(gè)時(shí)候。

這里寫圖片描述

所有分享大綱:Android進(jìn)階之旅 - 自定義View篇

視頻講解地址:http://pan.baidu.com/s/1kUGvvwj

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容