自定義下拉刷新和上拉加載框架

看過很多的下拉刷新框架,但感覺大多是基于ListView或RecyclerView捍岳。

個人覺得富寿,下拉上拉做為一個通用操作,最好是做為一個專門的容器锣夹,和視圖展示分離開來页徐,這樣就算內(nèi)容展示視圖要從ListView變成RecyclerViewl了,下拉上拉這一層银萍,也無需做任何改動变勇!

簡單效果圖

1.整體思路


  1. 自定義測量以及布局的方法
  1. 攔截掉子控件的一些手勢
  2. 處理手勢,刷新狀態(tài)
結(jié)構(gòu)圖

2.自定義測量和布局


繼承ViewGroup,重寫onMeasure與onLayout方法贴唇,這里要注意當(dāng)子控件GONE的情況

public abstract class DrawLayout extends ViewGroup {

    public View header;
    public View footer;

    public PullHeader pullHeader;
    public PullFooter pullFooter;

    public int bottomScroll;// 當(dāng)滾動到內(nèi)容最底部時Y軸所需要的滑動值
    public int lastChildIndex;// 最后一個childview的index

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

    public DrawLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setHeader(PullHeader pullHeader) {
        this.pullHeader = pullHeader;
    }

    public void setFooter(PullFooter pullFooter) {
        this.pullFooter = pullFooter;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        lastChildIndex = getChildCount() - 1;
    }

    /**
     * 添加上拉刷新布局作為header
     */
    public void addHeader(View header) {
        this.header = header;
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        addView(header, layoutParams);
    }

    /**
     * 添加下拉加載布局作為footer
     */
    public void addFooter(View footer) {
        this.footer = footer;
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        addView(footer, layoutParams);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 遍歷進行子視圖的測量工作
        for (int i = 0; i < getChildCount(); i++) {
            // 通知子視圖進行測量
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            }
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 重置(避免重復(fù)累加)
        int contentHeight = 0;

        // 遍歷進行子視圖的置位工作
        for (int index = 0; index < getChildCount(); index++) {
            View child = getChildAt(index);
            if (child.getVisibility() == GONE) {
                continue;
            }
            // 頭視圖隱藏在ViewGroup的頂端
            if (child == header) {
                child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);
            }
            // 尾視圖隱藏在ViewGroup所有內(nèi)容視圖之后
            else if (child == footer) {
                child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
            }
            // 內(nèi)容視圖根據(jù)定義(插入)順序,按由上到下的順序在垂直方向進行排列
            else {
                child.layout(0, contentHeight, child.getMeasuredWidth(), contentHeight + child.getMeasuredHeight());
                if (index <= lastChildIndex) {
                    if (child instanceof ScrollView) {
                        contentHeight += getMeasuredHeight();
                        continue;
                    }
                    contentHeight += child.getMeasuredHeight();
                }
            }
        }
        // 計算到達內(nèi)容最底部時ViewGroup的滑動距離
        bottomScroll = contentHeight - getMeasuredHeight();
    }
}

這里搀绣,我定義了兩個接口:
下拉接口:

public interface PullHeader {

    //下拉刷新(下拉中,到達有效刷新距離前)
    void onDownBefore(int scrollY);

    //松開刷新(下拉中戳气,到達有效刷新距離后)
    void onDownAfter(int scrollY);

    //準(zhǔn)備刷新(從松手后的位置滾動到刷新的位置)
    void onRefreshScrolling(int scrollY);

    //正在刷新……
    void onRefreshDoing(int scrollY);

    //刷新完成后链患,回到默認(rèn)狀態(tài)中
    void onRefreshCompleteScrolling(int scrollY, boolean isRefreshSuccess);

    //刷新取消后,回到默認(rèn)狀態(tài)中(沒有超過有效的下拉距離)
    void onRefreshCancelScrolling(int scrollY);
}

上拉接口:

public interface PullFooter {

    //上拉加載
    void onUpBefore(int scrollY);

    //松開加載
    void onUpAfter(int scrollY);

    //準(zhǔn)備加載
    void onLoadScrolling(int scrollY);

    //正在加載……
    void onLoadDoing(int scrollY);

    //加載完成后瓶您,回到默認(rèn)狀態(tài)中
    void onLoadCompleteScrolling(int scrollY, boolean isLoadSuccess);

    //加載取消后麻捻,回到默認(rèn)狀態(tài)中
    void onLoadCancelScrolling(int scrollY);
}

大家可以看到,我這里每一個回調(diào)中呀袱,都返回了scrollY贸毕,方便我們根據(jù)該值做一些自定義的動畫效果。

3.自定義攔截手勢


繼承剛才的DrawLayout夜赵,重寫onInterceptTouchEvent方法,這里主要是要解決當(dāng)子控件也可以滑動時的一些沖突問題明棍。

比如當(dāng)子控件是ScrollView時,只有當(dāng)ScrollView滑動到頂部或底部寇僧,不能再滑動時击蹲,才可以觸發(fā)下拉或上拉事件署拟。

public abstract class InterceptLauyout extends DrawLayout {

    // 用于計算滑動距離的Y坐標(biāo)中介
    public int lastYMove;
    // 用于判斷是否攔截觸摸事件的Y坐標(biāo)中介
    public int lastYIntercept;

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

    public InterceptLauyout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercept = false;
        // 記錄此次觸摸事件的y坐標(biāo)
        int y = (int) event.getY();
        // 判斷觸摸事件類型
        switch (event.getAction()) {
            // Down事件
            case MotionEvent.ACTION_DOWN: {
                // 記錄下本次系列觸摸事件的起始點Y坐標(biāo)
                lastYMove = y;
                // 不攔截ACTION_DOWN,因為當(dāng)ACTION_DOWN被攔截歌豺,后續(xù)所有觸摸事件都會被攔截
                intercept = false;
                break;
            }
            // Move事件
            case MotionEvent.ACTION_MOVE: {
                if (y > lastYIntercept) { // 下滑操作
                    // 獲取最頂部的子視圖
                    View child = getFirstVisiableChild();
                    if (child == null) {
                        intercept = false;
                    } else if (child instanceof AdapterView) {
                        intercept = avPullDownIntercept(child);
                    } else if (child instanceof ScrollView) {
                        intercept = svPullDownIntercept(child);
                    } else if (child instanceof RecyclerView) {
                        intercept = rvPullDownIntercept(child);
                    }
                } else if (y < lastYIntercept) { // 上拉操作
                    // 獲取最底部的子視圖
                    View child = getLastVisiableChild();
                    if (child == null) {
                        intercept = false;
                    } else if (child instanceof AdapterView) {
                        intercept = avPullUpIntercept(child);
                    } else if (child instanceof ScrollView) {
                        intercept = svPullUpIntercept(child);
                    } else if (child instanceof RecyclerView) {
                        intercept = rvPullUpIntercept(child);
                    }
                } else {
                    intercept = false;
                }
                break;
            }
            // Up事件
            case MotionEvent.ACTION_UP: {
                intercept = false;
                break;
            }
        }

        lastYIntercept = y;
        return intercept;
    }

    private View getLastVisiableChild() {
        for (int i = lastChildIndex; i >= 0; i--) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            } else {
                return child;
            }
        }
        return null;
    }

    private View getFirstVisiableChild() {
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child.getVisibility() == GONE) {
                continue;
            } else {
                return child;
            }
        }
        return null;
    }

    public boolean avPullDownIntercept(View child) {
        boolean intercept = true;
        AdapterView adapterChild = (AdapterView) child;
        // 判斷AbsListView是否已經(jīng)到達內(nèi)容最頂部
        if (adapterChild.getFirstVisiblePosition() != 0
                || adapterChild.getChildAt(0).getTop() != 0) {
            // 如果沒有達到最頂端推穷,則仍然將事件下放
            intercept = false;
        }
        return intercept;
    }

    public boolean avPullUpIntercept(View child) {
        boolean intercept = false;
        AdapterView adapterChild = (AdapterView) child;

        // 判斷AbsListView是否已經(jīng)到達內(nèi)容最底部
        if (adapterChild.getLastVisiblePosition() == adapterChild.getCount() - 1
                && (adapterChild.getChildAt(adapterChild.getChildCount() - 1).getBottom() == getMeasuredHeight())) {
            // 如果到達底部,則攔截事件
            intercept = true;
        }
        return intercept;
    }

    public boolean svPullDownIntercept(View child) {
        boolean intercept = false;
        if (child.getScrollY() <= 0) {
            intercept = true;
        }
        return intercept;
    }

    public boolean svPullUpIntercept(View child) {
        boolean intercept = false;
        ScrollView scrollView = (ScrollView) child;
        View scrollChild = scrollView.getChildAt(0);

        if (scrollView.getScrollY() >= (scrollChild.getHeight() - scrollView.getHeight())) {
            intercept = true;
        }
        return intercept;
    }

    public boolean rvPullDownIntercept(View child) {
        boolean intercept = false;

        RecyclerView recyclerChild = (RecyclerView) child;
        if (recyclerChild.computeVerticalScrollOffset() <= 0)
            intercept = true;

        return intercept;
    }

    public boolean rvPullUpIntercept(View child) {
        boolean intercept = false;

        RecyclerView recyclerChild = (RecyclerView) child;
        if (recyclerChild.computeVerticalScrollExtent() + recyclerChild.computeVerticalScrollOffset()
                >= recyclerChild.computeVerticalScrollRange())
            intercept = true;

        return intercept;
    }
}

4.自定義處理手勢类咧,刷新狀態(tài)


首先馒铃,我定義了下拉的所有狀態(tài),基本上這里的每一種狀態(tài)都對應(yīng)著上面的一種接口回調(diào)痕惋。

public enum PullStatus {
    DEFAULT,//默認(rèn)狀態(tài)

    DOWN_BEFORE,//下拉中区宇,到達有效刷新距離前
    DOWN_AFTER,//下拉中,到達有效刷新距離后
    REFRESH_SCROLLING,//放手后值戳,開始刷新前议谷,回到刷新的位置中
    REFRESH_DOING,//正在刷新中
    REFRESH_COMPLETE_SCROLLING,//刷新完成后,回到默認(rèn)狀態(tài)中
    REFRESH_CANCEL_SCROLLING,//刷新取消后堕虹,回到默認(rèn)狀態(tài)中

    UP_BEFORE,//上拉中卧晓,到達有效刷新距離前
    UP_AFTER,//上拉中,到達有效刷新距離后
    LOADMORE_SCROLLING,//放手后赴捞,開始加載前逼裆,從手勢位置回到加載的位置中
    LOADMORE_DOING,//正在加載中
    LOADMORE_COMPLETE_SCROLLING,//加載完成后,回到默認(rèn)狀態(tài)中
    LOADMORE_CANCEL_SCROLLING,//加載取消后赦政,回到默認(rèn)狀態(tài)中

}

接著胜宇,繼承剛才的InterceptLauyout,重寫onTouchEvent方法恢着,刷新狀態(tài)桐愉。這里我主要是通過屬性動畫+scrollTo/scrollBy來實現(xiàn)彈性滑動的。當(dāng)然你也可以用scroller來實現(xiàn)掰派。
需要注意scrollY和我們的視圖坐標(biāo)系方向相反仅财!

public class PullLayout extends InterceptLauyout {
    // 事件監(jiān)聽接口
    private OnPullListener listener;
    // Layout狀態(tài)
    private PullStatus status = PullStatus.DEFAULT;
    //阻尼系數(shù)
    private float damp = 0.5f;
    //恢復(fù)動畫的執(zhí)行時間
    public int SCROLL_TIME = 300;
    //是否刷新完成
    private boolean isRefreshSuccess = false;
    //是否加載完成
    private boolean isLoadSuccess = false;

    public void setOnPullListener(OnPullListener listener) {
        this.listener = listener;
    }

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

    public PullLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE: {
                // 計算本次滑動的Y軸增量(距離)
                int dy = y - lastYMove;
                LogUtil.print("dy=" + dy + "\tgetScrollY=" + getScrollY());
                // 如果getScrollY<0,即下拉操作
                if (getScrollY() < 0) {
                    if (header != null) {
                        // 進行Y軸上的滑動
                        performScroll(dy);
                        if (Math.abs(getScrollY()) > header.getMeasuredHeight()) {
                            updateStatus(DOWN_AFTER);
                        } else {
                            updateStatus(DOWN_BEFORE);
                        }
                    }
                }
                // 如果getScrollY>=0碗淌,即上拉操作
                else {
                    if (footer != null) {
                        // 進行Y軸上的滑動
                        performScroll(dy);
                        if (getScrollY() >= bottomScroll + footer.getMeasuredHeight()) {
                            updateStatus(UP_AFTER);
                        } else {
                            updateStatus(UP_BEFORE);
                        }
                    }
                }
                // 記錄y坐標(biāo)
                lastYMove = y;
                break;
            }

            case MotionEvent.ACTION_UP: {
                // 判斷本次觸摸系列事件結(jié)束時,Layout的狀態(tài)
                switch (status) {
                    //下拉刷新
                    case DOWN_BEFORE:
                        scrolltoDefaultStatus(REFRESH_CANCEL_SCROLLING);
                        break;
                    case DOWN_AFTER:
                        scrolltoRefreshStatus();
                        break;
                    //上拉加載更多
                    case UP_BEFORE:
                        scrolltoDefaultStatus(LOADMORE_CANCEL_SCROLLING);
                        break;
                    case UP_AFTER:
                        scrolltoLoadStatus();
                        break;
                    default:
                        LogUtil.print("松手時是其他狀態(tài):" + status);
                        break;
                }
            }
        }
        lastYIntercept = 0;
        postInvalidate();
        return true;
    }

    //刷新狀態(tài)
    private void updateStatus(PullStatus status) {
        this.status = status;
        int scrollY = getScrollY();
        LogUtil.print("status=" + status);
        // 判斷本次觸摸系列事件結(jié)束時,Layout的狀態(tài)
        switch (status) {
            //默認(rèn)狀態(tài)
            case DEFAULT:
                onDefault();
                break;
            //下拉刷新
            case DOWN_BEFORE:
                pullHeader.onDownBefore(scrollY);
                break;
            case DOWN_AFTER:
                pullHeader.onDownAfter(scrollY);
                break;
            case REFRESH_SCROLLING:
                pullHeader.onRefreshScrolling(scrollY);
                break;
            case REFRESH_DOING:
                pullHeader.onRefreshDoing(scrollY);
                listener.onRefresh();
                break;
            case REFRESH_COMPLETE_SCROLLING:
                pullHeader.onRefreshCompleteScrolling(scrollY, isRefreshSuccess);
                break;
            case REFRESH_CANCEL_SCROLLING:
                pullHeader.onRefreshCancelScrolling(scrollY);
                break;
            //上拉加載更多
            case UP_BEFORE:
                pullFooter.onUpBefore(scrollY);
                break;
            case UP_AFTER:
                pullFooter.onUpAfter(scrollY);
                break;
            case LOADMORE_SCROLLING:
                pullFooter.onLoadScrolling(scrollY);
                break;
            case LOADMORE_DOING:
                pullFooter.onLoadDoing(scrollY);
                listener.onLoadMore();
                break;
            case LOADMORE_COMPLETE_SCROLLING:
                pullFooter.onLoadCompleteScrolling(scrollY, isLoadSuccess);
                break;
            case LOADMORE_CANCEL_SCROLLING:
                pullFooter.onLoadCancelScrolling(scrollY);
                break;
        }
    }

    //默認(rèn)狀態(tài)
    private void onDefault() {
        isRefreshSuccess = false;
        isLoadSuccess = false;
    }

    //滾動到加載狀態(tài)
    private void scrolltoLoadStatus() {
        int start = getScrollY();
        int end = footer.getMeasuredHeight() + bottomScroll;
        performAnim(start, end, new AnimListener() {
            @Override
            public void onDoing() {
                updateStatus(LOADMORE_SCROLLING);
            }

            @Override
            public void onEnd() {
                updateStatus(LOADMORE_DOING);
            }
        });
    }

    //滾動到刷新狀態(tài)
    private void scrolltoRefreshStatus() {
        int start = getScrollY();
        int end = -header.getMeasuredHeight();
        performAnim(start, end, new AnimListener() {
            @Override
            public void onDoing() {
                updateStatus(REFRESH_SCROLLING);
            }

            @Override
            public void onEnd() {
                updateStatus(REFRESH_DOING);
            }
        });
    }

    //滾動到默認(rèn)狀態(tài)
    private void scrolltoDefaultStatus(final PullStatus startStatus) {
        int start = getScrollY();
        int end = 0;
        performAnim(start, end, new AnimListener() {
            @Override
            public void onDoing() {
                updateStatus(startStatus);
            }

            @Override
            public void onEnd() {
                updateStatus(DEFAULT);
            }
        });
    }

    //停止刷新
    public void stopRefresh(boolean isSuccess) {
        isRefreshSuccess = isSuccess;
        scrolltoDefaultStatus(PullStatus.REFRESH_COMPLETE_SCROLLING);
    }

    //停止加載更多
    public void stopLoadMore(boolean isSuccess) {
        isLoadSuccess = isSuccess;
        scrolltoDefaultStatus(PullStatus.LOADMORE_COMPLETE_SCROLLING);
    }

    //執(zhí)行滑動
    public void performScroll(int dy) {
        scrollBy(0, (int) (-dy * damp));
    }

    //執(zhí)行動畫
    private void performAnim(int start, int end, final AnimListener listener) {
        ValueAnimator animator = ValueAnimator.ofInt(start, end);
        animator.setDuration(SCROLL_TIME).start();
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                scrollTo(0, value);
                postInvalidate();
                listener.onDoing();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                listener.onEnd();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

    interface AnimListener {
        void onDoing();

        void onEnd();
    }

}

這里有一個供外部調(diào)用的監(jiān)聽器:

public interface OnPullListener {

    //執(zhí)行刷新
    void onRefresh();

    //執(zhí)行加載更多
    void onLoadMore();
}

5.在項目中使用時的基本配置


通過上面的代碼大家可以看到盏求,我的下拉刷新框架,沒有依賴任何res資源亿眠,很方便大家copy碎罚,或者直接依賴jar。

一般整個應(yīng)用會有一個統(tǒng)一的下拉刷新效果和上拉加載效果纳像,所以荆烈,我們可以再自定義一個應(yīng)用的刷新布局,繼承自PullLayout。

public class RefreshLayout extends PullLayout {
    public RefreshLayout(Context context) {
        super(context);
    }

    public RefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        init();
    }

    public void init() {
        HeaderView header = new HeaderView(getContext());
        FooterView footer = new FooterView(getContext());

        addHeader(header);
        addFooter(footer);

        setHeader(header);
        setFooter(footer);
    }

}

這里的HeaderView和FooterView分別實現(xiàn)了PullHeader和PullFooter接口憔购。

public class HeaderView extends FrameLayout implements PullHeader {

    public TextView tvPullDown;

    public HeaderView(Context context) {
        super(context);
        LayoutInflater.from(context).inflate(R.layout.layout_header, this, true);
        tvPullDown = (TextView) findViewById(R.id.tv);
    }

    @Override
    public void onDownBefore(int scrollY) {
        tvPullDown.setText("下拉刷新");
    }

    @Override
    public void onDownAfter(int scrollY) {
        tvPullDown.setText("松開刷新");
    }

    @Override
    public void onRefreshScrolling(int scrollY) {
        tvPullDown.setText("準(zhǔn)備刷新");
    }

    @Override
    public void onRefreshDoing(int scrollY) {
        tvPullDown.setText("正在刷新……");
    }

    @Override
    public void onRefreshCompleteScrolling(int scrollY, boolean isLoadSuccess) {
        tvPullDown.setText(isLoadSuccess ? "刷新成功" : "刷新失敗");
    }

    @Override
    public void onRefreshCancelScrolling(int scrollY) {
        tvPullDown.setText("取消刷新");
    }
}
public class FooterView extends FrameLayout implements PullFooter {

    public TextView tvPullUp;

    public FooterView(Context context) {
        super(context);
        LayoutInflater.from(context).inflate(R.layout.layout_footer, this, true);
        tvPullUp = (TextView) findViewById(R.id.tv);
    }


    @Override
    public void onUpBefore(int scrollY) {
        tvPullUp.setText("上拉加載更多");
    }

    @Override
    public void onUpAfter(int scrollY) {
        tvPullUp.setText("松開加載更多");
    }

    @Override
    public void onLoadScrolling(int scrollY) {
        tvPullUp.setText("準(zhǔn)備加載");
    }

    @Override
    public void onLoadDoing(int scrollY) {
        tvPullUp.setText("正在加載……");
    }

    @Override
    public void onLoadCompleteScrolling(int scrollY, boolean isLoadSuccess) {
        tvPullUp.setText(isLoadSuccess ? "加載成功" : "加載失敗");
    }

    @Override
    public void onLoadCancelScrolling(int scrollY) {
        tvPullUp.setText("加載取消");
    }
}

它們的布局資源也是很簡單的:
layout_header:

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#f5f5f5">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="@dimen/header_height"
        android:gravity="center"
        android:text="下拉刷新"
        android:textColor="#000000"
        android:textSize="16sp"/>

</RelativeLayout>

layout_footer:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#f5f5f5">

    <TextView
        android:id="@+id/tv"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="@dimen/header_height"
        android:text="上拉加載更多"
        android:textColor="#000000"
        android:textSize="16sp"/>

</RelativeLayout>

6.加個輔助類宫峦,讓集成變得更加簡單


本來呢,寫到第5點玫鸟,其實就不用寫了导绷,不過,考慮到有時候屎飘,一個應(yīng)用中多個界面都有下拉刷新時妥曲,經(jīng)常會有許多類似的代碼塊,所以钦购,我又定義了一個輔助類檐盟。

這里的SingleAdapter、SuperViewHolder是使用的我的另一個庫:fast-adapter:Adapter的封裝之路押桃,

而IWebLoading葵萎、WebTransformer、WebSubscriber等呢唱凯,則是與我的另一個庫:fast-http有關(guān)羡忘,這是一個Retrofit+OkHttp+RxJava的封裝庫,暫時沒寫博客仔細(xì)整理波丰,以后再分享,大家可以不用管它舶得,那個與本節(jié)無關(guān)掰烟,IWebLoading是為了讓初始數(shù)據(jù)時有l(wèi)oading,刷新或加載更多時沒有l(wèi)oading。WebTransformer是為了通用的線程切換沐批。WebSubscriber是為了通用的錯誤處理纫骑。大家可以先看看我的這兩篇:Retrofit基本用法和流程分析Okhttp基本用法和流程分析

public class RefreshHelper<T> {

    public RefreshLayout viewRefresh;
    public RefreshInterface<T> refreshInterface;
    public int layoutId;

    public Context context;
    public IWebLoading webLoading;
    public View viewEmpty;
    public RecyclerView rv;
    public List<T> data;

    public SingleAdapter<T> adapter;
    public int curPage = 1;//當(dāng)前頁碼
    public boolean isRefresh = false;//是否正在刷新
    public boolean isLoadMore = false;//是否正在加載更多

    public RefreshHelper(RefreshLayout viewRefresh,RefreshInterface<T> refreshInterface,int layoutId) {
        this.viewRefresh=viewRefresh;
        this.refreshInterface=refreshInterface;
        this.layoutId=layoutId;

        context=viewRefresh.getContext();
        webLoading=new LoadingDialog(context);
        viewEmpty=viewRefresh.getChildAt(0);
        rv= (RecyclerView) viewRefresh.getChildAt(1);
        data = new ArrayList<>();

        initRv();
        initRefresh();
        loadData();
    }

    private void initRv() {
        LinearLayoutManager layoutManager = new LinearLayoutManager(context);
        rv.setLayoutManager(layoutManager);
        adapter = new SingleAdapter<T>(context, layoutId) {
            @Override
            protected void bindData(SuperViewHolder holder, T item) {
                refreshInterface.bindData(holder, item);
            }
        };
        rv.setAdapter(adapter);
    }

    private void initRefresh() {
        viewRefresh.setOnPullListener(new OnPullListener() {
            @Override
            public void onRefresh() {
                LogUtil.print("");
                isRefresh = true;
                curPage = 1;
                data = new ArrayList<>();
                loadData();
            }

            @Override
            public void onLoadMore() {
                LogUtil.print("");
                isLoadMore = true;
                curPage++;
                loadData();
            }
        });
    }

    private void loadData() {
        if (isRefresh || isLoadMore) {
            webLoading = null;
        }
        refreshInterface.getData(curPage)
                .compose(new WebTransformer<>(webLoading))
                .subscribe(new WebSubscriber<List<T>>(webLoading) {
                    @Override
                    public void onSuccess(List<T> list) {
                        LogUtil.print("list.size="+list.size());

                        if (isRefresh) {
                            isRefresh = false;
                            viewRefresh.stopRefresh(true);
                        }
                        if (isLoadMore) {
                            if (list.isEmpty()) {
                                AppHelper.show("沒有更多數(shù)據(jù)了");
                            }
                            isLoadMore = false;
                            viewRefresh.stopLoadMore(true);
                        }
                        data.addAll(list);
                        if (data.isEmpty()) {
                            rv.setVisibility(View.GONE);
                            viewEmpty.setVisibility(View.VISIBLE);
                        } else {
                            rv.setVisibility(View.VISIBLE);
                            viewEmpty.setVisibility(View.GONE);
                            adapter.setData(data);
                        }
                    }

                    @Override
                    public void onFailure(WebException exception) {
                        super.onFailure(exception);
                        if (isRefresh) {
                            isRefresh = false;
                            viewRefresh.stopRefresh(false);
                        }
                        if (isLoadMore) {
                            isLoadMore = false;
                            viewRefresh.stopLoadMore(false);
                        }
                    }
                });
    }


    public interface RefreshInterface<T> {

        void bindData(SuperViewHolder holder, T item);

        Observable<List<T>> getData(int curPage);

    }

    public List<T> getData() {
        return data;
    }

    public SingleAdapter<T> getAdapter() {
        return adapter;
    }
}

這里注意九孩,RefreshLayout里面的子控件必須是這樣的先馆,第一個是viewEmpty,第二個是recyclerView(當(dāng)然,你要是其它的躺彬,也可以煤墙,改輔助類吧,我這個輔助類只針對recyclerView):

    <com.che.lovecar.support.view.pull.RefreshLayout
        android:id="@+id/view_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/view_empty"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="vertical"
            android:visibility="gone">

            <ImageView
                android:layout_width="150dp"
                android:layout_height="150dp"
                android:scaleType="centerInside"
                android:src="@drawable/icon_nomessage"/>

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="暫無消息"
                android:textColor="@color/text_d"
                android:textSize="20sp"/>

        </LinearLayout>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_msg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never"
            android:visibility="visible"/>

    </com.che.lovecar.support.view.pull.RefreshLayout>

然后宪拥,在Activity或Fragment中使用時仿野,我只需要這樣:
1.添加一個RefreshHelper

private RefreshHelper<Message> refreshHelper;

refreshHelper = new RefreshHelper<Message>(viewRefresh, this, R.layout.item_msg);

2.Activity實現(xiàn)RefreshInterface接口,實現(xiàn)getData和bindData方法

public class MsgListActivity extends BaseActivity implements RefreshHelper.RefreshInterface<Message>
    @Override
    public void bindData(SuperViewHolder holder, Message item) {
        View rootView = holder.getRootView();
        View dot = holder.getView(R.id.dot_msg);
        TextView tvTime = holder.getView(R.id.tv_time);
        TextView tvMsg = holder.getView(R.id.tv_msg);

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");

        dot.setVisibility(item.getRead() == 0 ? View.VISIBLE : View.GONE);
        tvTime.setText(dateFormat.format(item.getCreate_time()));
        tvMsg.setText(item.getContent());
        rootView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                readMsg(item, dot);
            }
        });
    }

    @Override
    public Observable<List<Message>> getData(int curPage) {
        return Observable.create(subscriber -> {
            if (subscriber.isUnsubscribed()) return;
            try {
                Thread.sleep(3000);
                List<Message> list = new ArrayList<>();
                if (!isEmpty) {
                    LogUtil.print("加載本地的json");
                    String json = FileUtil.getFromAssets(getActivity(), "json/list.json");
                    MessageListResponse response = JSON.parseObject(json, MessageListResponse.class);
                    list = response.getMessageListPojoList();
                }
                subscriber.onNext(list);
                subscriber.onCompleted();
            } catch (Exception e) {
                subscriber.onError(e);
            }
        });
    }

參考目錄:


  1. 自個兒寫Android的下拉刷新/上拉加載控件
  2. android-Ultra-Pull-To-Refresh 源碼解析
  3. Android下拉刷新完全解析,教你如何一分鐘實現(xiàn)下拉刷新功能
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末她君,一起剝皮案震驚了整個濱河市脚作,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖球涛,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件劣针,死亡現(xiàn)場離奇詭異,居然都是意外死亡亿扁,警方通過查閱死者的電腦和手機捺典,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來魏烫,“玉大人辣苏,你說我怎么就攤上這事『灏” “怎么了稀蟋?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長呐赡。 經(jīng)常有香客問我退客,道長,這世上最難降的妖魔是什么链嘀? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任萌狂,我火速辦了婚禮,結(jié)果婚禮上怀泊,老公的妹妹穿的比我還像新娘茫藏。我一直安慰自己,他們只是感情好霹琼,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布务傲。 她就那樣靜靜地躺著,像睡著了一般枣申。 火紅的嫁衣襯著肌膚如雪售葡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天忠藤,我揣著相機與錄音挟伙,去河邊找鬼。 笑死模孩,一個胖子當(dāng)著我的面吹牛尖阔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播榨咐,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诺祸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了祭芦?” 一聲冷哼從身側(cè)響起筷笨,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后胃夏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轴或,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年仰禀,在試婚紗的時候發(fā)現(xiàn)自己被綠了照雁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡答恶,死狀恐怖饺蚊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悬嗓,我是刑警寧澤污呼,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站包竹,受9級特大地震影響燕酷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜周瞎,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一苗缩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧声诸,春花似錦酱讶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至囤攀,卻和暖如春软免,著一層夾襖步出監(jiān)牢的瞬間宫纬,已是汗流浹背焚挠。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留漓骚,地道東北人蝌衔。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像蝌蹂,于是被迫代替她去往敵國和親噩斟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容