自定義SwipeRefreshLayout科阎,一個(gè)能同時(shí)支持RecyclerView和ListView的下拉刷新和上拉加載的布局

??之前我們介紹過RecyclerView和SwipeRefreshLayout的交互工作模式递递,現(xiàn)在楼镐,讓我們擴(kuò)展一下SwipeRefreshLayout的功能讓它能支持上拉加載癞志,并且能和原生的SwipeRefreshLayout效果切換。

使用RecyclerView的延展樣式
使用RecyclerView并使用SwipeRefreshLayout的原生樣式
使用ListView的延展樣式
使用ListView并使用SwipeRefreshLayout的原生樣式

1.功能

  • 支持RecyclerView和ListView下拉刷新和上拉加載

  • 支持主動刷新

  • 下拉刷新能使用SwipeRefreshLayout的原生樣式或自定義的延展樣式

  • 可自定義ViewGroup定制刷新樣式和加載樣式

2.說明

??總體來說框产,因?yàn)镾wipeRefreshLayout本身就支持RecyclerView和ListView的下拉刷新功能凄杯,并且一定程度上支持嵌套滑動(對ListView無法支持,當(dāng)RecyclerView開啟嵌套滑動時(shí)可以支持)秉宿,所以我們這個(gè)自定義控件繼承SwipeRefreshLayout實(shí)現(xiàn)是相當(dāng)方便的戒突,但我們還要考慮到以下這些情況,這也是我們這個(gè)自定義控件的主要思路蘸鲸。

  • 要實(shí)現(xiàn)延展樣式,需要在RecyclerView和ListView中加入headView和footView動態(tài)改變它們的高度窿锉,當(dāng)控件處于最頂端繼續(xù)下拉時(shí)酌摇,讓headView的height隨手勢的滑動而變化膝舅,當(dāng)控件處于最底端繼續(xù)上拉時(shí)讓footView的height隨滑動而變化。

  • ListView本身支持添加headView和footView窑多,但RecyclerView不支持仍稀,所以需要重寫RecyclerView.Adapter

  • RecyclerView是否開啟嵌套滑動決定了我們實(shí)現(xiàn)RecyclerView下拉刷新和上拉加載功能的難易程度,開啟嵌套滑動時(shí)我們可以直接利用SwipeRefreshLayout支持的嵌套滑動方法來實(shí)現(xiàn)埂息。其他情況都需要攔截事件進(jìn)行滑動處理技潘,我們看一下下面這張圖對于事件進(jìn)行攔截處理的分類情況

??從圖中我們可以具體看到什么情況下需要攔截事件,我們這個(gè)自定義SwipeRefreshLayout實(shí)現(xiàn)的關(guān)鍵地方就是利用嵌套滑動或者事件攔截來處理千康,下面進(jìn)入正題看下到底該怎么做享幽。

  • 類的說明
描述
SwipeRefreshLoadLayout 繼承SwipeRefreshLayout,增加上拉加載功能以及各種事件的處理拾弃,整個(gè)項(xiàng)目的和心類
RecycleViewAdapter 如果要用RecyclerView實(shí)現(xiàn)功能值桩,使用此類代替原RecyclerView.Adapter
SwipeLinearLayoutManager RecyclerView布局管理器,繼承LinearLayoutManager豪椿,一個(gè)輔助類奔坟,使用RecyclerView時(shí)必須使用此類
Swipe 內(nèi)部有公開和非公開的監(jiān)聽接口
ListViewHeadAndFootManager ListView的headView和footView管理器

3.通過嵌套滑動方式實(shí)現(xiàn)

??我們從最簡單的地方入手,就是當(dāng)RecyclerView開啟嵌套滑動功能的情況下實(shí)現(xiàn)延展樣式搭盾,這個(gè)時(shí)候SwipeRefreshLayout能監(jiān)聽到RecyclerVIew是否滑動到了最頂端或最底部咳秉,我們需要覆寫它下面幾個(gè)方法:

  • void onNestedScrollAccepted(View child, View target, int axes):開啟嵌套滑動功能后才會調(diào)用,僅在滑動開始時(shí)調(diào)用一次鸯隅,可進(jìn)行滑動相關(guān)數(shù)據(jù)的初始化操作澜建。
  • void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed):當(dāng)RecyclerView滑動到頂端并繼續(xù)下拉或者滑動到底部并繼續(xù)上拉時(shí)SwipeRefreshLayout會調(diào)用此方法,覆寫此方法后可通過dyUnconsumed參數(shù)獲取到當(dāng)前滑動的距離滋迈,從而計(jì)算出headView或footView的height增大的值霎奢。
  • void onNestedPreScroll(View target, int dx, int dy, int[] consumed):當(dāng)從下拉刷新或上拉加載狀態(tài)返回時(shí),SwipeRefreshLayout會調(diào)用此方法饼灿,覆寫此方法可計(jì)算headView或footView的height減小的值幕侠。
  • void onStopNestedScroll(View target):手指離開屏幕時(shí)SwipeRefreshLayout會調(diào)用此方法,覆寫此方法我們可以從這個(gè)狀態(tài)開始計(jì)算headView或footView回收時(shí)其height的值碍彭。

??同理晤硕,按照這個(gè)思路,上拉加載也完全可以通過覆寫這四個(gè)方法實(shí)現(xiàn)庇忌,當(dāng)然舞箍,這是在RecyclerView開啟嵌套滑動的前提下,我們結(jié)合代碼看一下這部分的具體實(shí)現(xiàn)皆疹。

覆寫SwipeRefreshLayout類中四個(gè)關(guān)于嵌套滑動的方法

//下拉刷新時(shí)手指的滑動距離疏橄,意味著headView的height需要改變的大小
private int mTotalUnconsumed;

//上拉加載時(shí)手指的滑動距離,意味著footView的height需要改變的大小
private int mTotalUnconsumed2;

@Override
public void onNestedScrollAccepted(View child, View target, int axes) {
    super.onNestedScrollAccepted(child, target, axes);
    mTotalUnconsumed = 0;
    mTotalUnconsumed2 = 0;
}

/**
 * 下拉刷新返回或者上拉加載返回時(shí)需要覆寫的方法,
 * 通過dy計(jì)算headView或者footView的height值捎迫,減小其高度晃酒,
 * 通過consumed[]改變RecyclerView的滑動距離,使之不會滑動的過快造成滑動效果降低窄绒,
 * 這里我們通過下拉刷新樣式?jīng)Q定是調(diào)用父類的方法還是自己的方法贝次,如果時(shí)需要原生樣式,直接調(diào)用父類方法彰导,
 * 否則調(diào)用自己的方法
 */
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
    if (refreshStyle == CIRCLE) {
        super.onNestedPreScroll(target, dx, dy, consumed);
    } else if (refreshStyle == SPREAD) {
        if (dy > 0 && mTotalUnconsumed > 0) {
            //下拉刷新返回操作
            if (dy > mTotalUnconsumed) {
                consumed[1] = dy - mTotalUnconsumed;
                mTotalUnconsumed = 0;
                finishParentDrag();
            } else {
                mTotalUnconsumed -= dy;
                consumed[1] = dy;
            }
            onPullDownBack(dy);
        }
    }
    //上拉加載返回操作
    if (dy < 0 && mTotalUnconsumed2 < 0) {
        if (dy < mTotalUnconsumed2) {
            consumed[1] = mTotalUnconsumed2 - dy;
            mTotalUnconsumed2 = 0;
            finishParentDrag();
        } else {
            mTotalUnconsumed2 -= dy;
            consumed[1] = dy;
        }
        onPullUpBack(dy);
    }
}

/**
 * 下拉刷新或者上拉加載時(shí)需要覆寫的方法蛔翅,
 * 通過dyUnconsumed計(jì)算headView或者footView的height值,增大其高度位谋,
 * 通過下拉刷新樣式?jīng)Q定是調(diào)用父類的方法還是自己的方法山析,如果時(shí)需要原生樣式,直接調(diào)用父類方法倔幼,
 * 否則調(diào)用自己的方法
 */
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
    if (refreshStyle == CIRCLE) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    } else if (refreshStyle == SPREAD) {
        //下拉刷新操作
        if (dyUnconsumed < 0 && !isRefreshing) {
            mTotalUnconsumed += -dyUnconsumed;
            onPullDown(dyUnconsumed);
        }
    }
    //上拉加載操作
    if (dyUnconsumed > 0 && !isLoading && footViewVisibility == VISIBLE) {
        mTotalUnconsumed2 += -dyUnconsumed;
        onPullUp(dyUnconsumed);
    }
}

/**
 * 手指釋放時(shí)調(diào)用盖腿,開啟一個(gè)動畫讓headView或者footView的height恢復(fù)
 */
@Override
public void onStopNestedScroll(View target) {
    if (refreshStyle == CIRCLE) {
        super.onStopNestedScroll(target);
    } else if (refreshStyle == SPREAD) {
        //下拉刷新釋放操作
        if (mTotalUnconsumed > 0 && !isRefreshing) {
            release();
            mTotalUnconsumed = 0;
        }
    }
    //上拉加載釋放操作
    if (mTotalUnconsumed2 < 0) {
        release();
        mTotalUnconsumed2 = 0;
    }
}

??對于以上代碼來說,我們加入了下拉刷新的樣式判斷损同,如果樣式是CIRCLE即SwipeRefreshLayout原生樣式翩腐,那就直接調(diào)用父方法,否則如果是SPREAD即延展樣式膏燃,我們才需要調(diào)用我們自己的實(shí)現(xiàn)代碼茂卦。

??下面是下拉刷新或上拉加載時(shí)執(zhí)行的代碼,會分別調(diào)用onPullDown(int dy)组哩、onPullDownBack(int dy)等龙、onPullUp(int dy)、onPullUpBack(int dy)方法伶贰,然后通過Swipe.OnChangeViewHeight接口讓headView或footView的height發(fā)生變化改變它的高度蛛砰,這里只貼一小段代碼,具體的可查看項(xiàng)目源碼黍衙。

SwipeRefreshLoadLayout中:

/**
 * 下拉刷新
 */
private void onPullDown(int dy) {
    dragAction = DRAG_ACTION_PULL_DOWN;
    pullDownDistance += Math.abs(dy) / 2;
    changeTipsRefresh();
    if (onChangeViewHeight != null) {
        onChangeViewHeight.changeHeadViewHeight(pullDownDistance);
    }
}

RecycleViewAdapter實(shí)現(xiàn)了Swipe.OnChangeViewHeight接口泥畅,其中代碼:

@Override
public void changeHeadViewHeight(int headViewHeight) {
    if (headViewHolder.headView != null && headViewHeight >= 0) {
        headViewLayoutParams.height = headViewHeight;
        headViewHolder.headView.setLayoutParams(headViewLayoutParams);
    }
}

??現(xiàn)在,通過SwipeRefreshLayout的幫助我們RecyclerView的下拉刷新和上拉加載已經(jīng)實(shí)現(xiàn)了琅翻,但當(dāng)RecyclerView禁止嵌套滑動時(shí)位仁,上面的四個(gè)方法就會無法使用,并且如果我們要實(shí)現(xiàn)ListView的這一功能方椎,我們必須要通過事件攔截來實(shí)現(xiàn)聂抢。

4.通過事件攔截方式實(shí)現(xiàn)

??事件分發(fā)我們不多做介紹了,這里就兩點(diǎn)要說的棠众,什么時(shí)候攔截事件琳疏,攔截以后要做什么,這是我們這篇自定義ViewGroup要面臨的問題。在RecyclerView中空盼,我們通過View類的boolean canScrollVertically(int direction)方法確定它是否滑到了最頂端或最底部疮薇。在ListView中,我們通過ListViewCompat類的boolean canScrollList(ListView listView, int direction)判斷它是否滑到了最頂端或最底部我注,有了這兩個(gè)方法,我們在onInterceptTouchEvent(MotionEvent ev)
中進(jìn)行判斷即可迟隅,符合情況的我們返回true然后交給onTouchEvent(MotionEvent ev)處理但骨,不符合的直接調(diào)用父方法即可。

@Override
public boolean onTouchEvent(MotionEvent ev) {
    if (action == null) {
        return super.onTouchEvent(ev);
    }
    switch (ev.getAction()) {
        case MotionEvent.ACTION_MOVE:
            float currentY = ev.getY();
            int dy = (int) (currentY - initialDownY);
            if (dy >= 0) {//dy>0說明此時(shí)執(zhí)行的是下拉刷新或者上拉加載返回操作
                if (action == ACTION_PULL_DOWN && !isRefreshing) {
                    onPullDown(dy);
                } else if (action == ACTION_PULL_UP && !isLoading) {
                    onPullUpBack(dy);
                    if (recyclerView != null) {
                        recyclerView.scrollBy(0, dy);
                    }
                }
            } else {//dy<0說明此時(shí)執(zhí)行的是上拉加載或者下拉刷新返回操作
                if (action == ACTION_PULL_DOWN && !isRefreshing) {
                    onPullDownBack(dy);
                } else if (action == ACTION_PULL_UP && !isLoading && footViewVisibility == VISIBLE) {
                    onPullUp(dy);
                    if (recyclerView != null) {
                        recyclerView.scrollBy(0, -dy);
                    }
                    if (listView != null) {
                        ListViewCompat.scrollListBy(listView, -dy);
                    }
                }
            }
            initialDownY = currentY;
            break;
        case MotionEvent.ACTION_UP:
            action = null;
            release();
            break;
    }
    return action != null || super.onTouchEvent(ev);
}

private float initialDownY;
private final int ACTION_PULL_DOWN = 0X00C1;
private final int ACTION_PULL_UP = 0X00D1;
private Integer action = null;

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (recyclerView != null) {
        //當(dāng)刷新類型為CIRCLE且處于刷新狀態(tài)時(shí)智袭,recyclerView的嵌套滑動不再響應(yīng)奔缠,所以無法進(jìn)行上拉加載,因此通過攔截事件利用onTouchEvent()實(shí)現(xiàn)上拉加載
        if (refreshStyle == CIRCLE && isRefreshing && !recyclerView.canScrollVertically(1)) {
            Boolean x = isIntercept(ev, true);
            if (x != null) return x;
        }
        //當(dāng)recyclerView禁止嵌套滑動時(shí)吼野,上拉加載需要使用onTouchEvent()實(shí)現(xiàn)校哎,所以這里要進(jìn)行攔截
        if (!recyclerView.isNestedScrollingEnabled() && !recyclerView.canScrollVertically(1)) {
            Boolean x = isIntercept(ev, true);
            if (x != null) return x;
        }
        //當(dāng)刷新類型為SPREAD時(shí),如果recyclerView禁止了嵌套滑動瞳步,那這里需要攔截事件讓onTouchEvent()實(shí)現(xiàn)下拉刷新
        if (refreshStyle == SPREAD && !recyclerView.isNestedScrollingEnabled() && !recyclerView.canScrollVertically(-1)) {
            Boolean x = isIntercept(ev, false);
            if (x != null) return x;
        }
    }
    if (listView != null) {
        if (refreshStyle == CIRCLE && isRefreshing && !ListViewCompat.canScrollList(listView, 1)) {
            Boolean x = isIntercept(ev, true);
            if (x != null) return x;
        }
        if (!ListViewCompat.canScrollList(listView, 1)) {
            Boolean x = isIntercept(ev, true);
            if (x != null) return x;
        }
        if (refreshStyle == SPREAD && !ListViewCompat.canScrollList(listView, -1)) {
            Boolean x = isIntercept(ev, false);
            if (x != null) return x;
        }
    }
    return super.onInterceptTouchEvent(ev);
}

@Nullable
private Boolean isIntercept(MotionEvent ev, boolean type) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            initialDownY = ev.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            float y = ev.getY();
            if (type) {
                if (y - initialDownY < 0) {//判斷上滑或者下滑操作
                    initialDownY = y;
                    action = ACTION_PULL_UP;
                    return true;
                }
            } else {
                if (y - initialDownY > 0) {
                    initialDownY = y;
                    action = ACTION_PULL_DOWN;
                    return true;
                }
            }
            break;
    }
    return null;
}

??上面這段代碼實(shí)現(xiàn)了何時(shí)攔截事件的邏輯闷哆,以及攔截后的相應(yīng)處理。當(dāng)然单起,這只是我個(gè)人實(shí)現(xiàn)的一種方式或許并不適用于所有人抱怔,對于每個(gè)人而言都各有一種實(shí)現(xiàn)方式,所以我也不準(zhǔn)備強(qiáng)行解釋上面的代碼了嘀倒,以上這段代碼只作為參考屈留,提供一種實(shí)現(xiàn)思路。

??我們可以看到测蘑,在事件攔截后灌危,將相應(yīng)的操作交給了onTouchEvent()方法處理,在onTouchEvent()方法中碳胳,通過判斷上下滑動勇蝙,確定手指滑動的距離,調(diào)用onPullDown(int dy)固逗、onPullDownBack(int dy)浅蚪、onPullUp(int dy)、onPullUpBack(int dy)方法來改變headView和footView的高度烫罩,從而達(dá)到之前利用嵌套滑動方式同樣的效果惜傲。

??現(xiàn)在,我們實(shí)現(xiàn)了圖中所有情況的下拉刷新和上拉加載操作贝攒,我們再看看其他幾個(gè)類盗誊。

5.RecycleViewAdapter類

??我們知道在RecyclerView中需要手動添加headView和footView,所以我們需要繼承RecyclerView.Adapter實(shí)現(xiàn)一個(gè)自己的Adapter,在這個(gè)Adapter中哈踱,需要實(shí)現(xiàn)原有的Adapter抽象方法荒适,并再次抽象出相應(yīng)的方法供外部使用,看一下該類中和原有Adapter的對應(yīng)方法开镣, 我們使用時(shí)只需要實(shí)現(xiàn)這幾個(gè)新的抽象方法即可刀诬。

RecyclerView.Adapter的原有方法 RecycleViewAdapter中對應(yīng)的方法
int getItemCount() int getCounts()
RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) RecyclerView.ViewHolder onNewViewHolder(@NonNull ViewGroup parent, int viewType)
void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) void onSetViewHolder(@NonNull RecyclerView.ViewHolder holder, int position)
int getItemViewType(int position) int getItemType(int position)
  • 有一點(diǎn)要注意,在使用RecyclerView.Adapter類中其他沒有覆寫過的方法(也就是除了上述表中的其他方法)時(shí)要注意position邪财,因?yàn)槲覀兊腞ecyclerView默認(rèn)已經(jīng)有兩個(gè)View了陕壹,headView和footView,所以在碰到有方法中有position時(shí)要注意當(dāng)前position具體是哪一個(gè)树埠,一般真正的值是position-1糠馆,因?yàn)橐獪p去一個(gè)headView。

自定義headView或footView

??此外怎憋,在此類中額外實(shí)現(xiàn)了一個(gè)功能又碌,那就是可以自定義ViewGrou實(shí)現(xiàn)延展樣式的headView和footView,如果不滿意我實(shí)現(xiàn)的樣式绊袋,可以自己定義毕匀。實(shí)現(xiàn)此功能的代碼如下:

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    if (viewType == VIEW_TYPE_FOOT) {
        if (footViewHolder == null) {
        //在這里判斷一下是否有自定義的ViewGroup作為headView或footView
            if (mySwipe.getFootView() == null) {
                footViewHolder = new FootViewHolder(LayoutInflater.from(mContext).inflate(R.layout.srll_foot, null, false), true);
            } else {
                footViewHolder = new FootViewHolder(mySwipe.getFootView(), false);
            }
        }
        return footViewHolder;
    } else if (viewType == VIEW_TYPE_HEAD) {
        if (headViewHolder == null) {
            if (mySwipe.getHeadView() == null) {
                headViewHolder = new HeadViewHolder(LayoutInflater.from(mContext).inflate(R.layout.srll_head, null, true), true);
            } else {
                headViewHolder = new HeadViewHolder(mySwipe.getHeadView(), false);
            }
        }
        return headViewHolder;
    }
    return onNewViewHolder(parent, viewType);
}

private class FootViewHolder extends RecyclerView.ViewHolder {
    private TextView tvFootTip;
    private ViewGroup footView;
    private ImageView ivFootRefresh;

    private FootViewHolder(View itemView, boolean isFromSelf) {
        super(itemView);
        this.footView = (ViewGroup) itemView;
        this.footView.setVisibility(mySwipe.footViewVisibility);
        if (isFromSelf) {
            ViewGroup childAt = (ViewGroup) footView.getChildAt(0);
            ViewGroup.LayoutParams childLayoutParams = childAt.getLayoutParams();
            childLayoutParams.height = refreshViewHeight;
            //設(shè)置底部加載子視圖高度
            childAt.setLayoutParams(childLayoutParams);
            this.tvFootTip = footView.findViewById(R.id.tv_foot_tip);
            this.ivFootRefresh = footView.findViewById(R.id.iv_foot_refresh);
            this.ivFootRefresh.animate().setInterpolator(new LinearInterpolator());
            LinearLayout llLoadMore = footView.findViewById(R.id.ll_load_more);
            llLoadMore.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    mySwipe.doLoadMore();
                }
            });
        }
        //設(shè)置底部加載父視圖高度
        footViewLayoutParams = new RecyclerView.LayoutParams(-1, refreshViewHeight);
        footView.setLayoutParams(footViewLayoutParams);
    }
}

private class HeadViewHolder extends RecyclerView.ViewHolder {
    private ViewGroup headView;
    private TextView tvHeadTip;
    private ImageView ivHeadArrow;
    private TextView tvRefreshTime;
    private ImageView ivHeadRefresh;

    private HeadViewHolder(View itemView, boolean isFromSelf) {
        super(itemView);
        this.headView = (ViewGroup) itemView;
        if (isFromSelf) {
            ViewGroup childAt = (ViewGroup) headView.getChildAt(0);
            ViewGroup.LayoutParams childLayoutParams = childAt.getLayoutParams();
            childLayoutParams.height = refreshViewHeight;
            childAt.setLayoutParams(childLayoutParams);
            this.tvHeadTip = headView.findViewById(R.id.tv_head_tip);
            this.ivHeadArrow = headView.findViewById(R.id.iv_head_arrow);
            this.ivHeadArrow.animate().setInterpolator(new LinearInterpolator());
            this.ivHeadRefresh = headView.findViewById(R.id.iv_head_refresh);
            this.ivHeadRefresh.animate().setInterpolator(new LinearInterpolator());
            this.tvRefreshTime = headView.findViewById(R.id.tv_refresh_time);
            if (!TextUtils.isEmpty(Swipe.getLastRefreshTime(mContext))) {
                this.tvRefreshTime.setText("最后更新:" + Swipe.getLastRefreshTime(mContext));
            } else {
                this.tvRefreshTime.setVisibility(View.GONE);
            }
        }
        headViewLayoutParams = new RecyclerView.LayoutParams(-1, 0);
        headView.setLayoutParams(headViewLayoutParams);
    }
}

??原理很簡單,在onCreateViewHolder()中癌别,先判斷是否有自定義的ViewGroup作為headView或者footView期揪,如果有,傳入ViewHolder中规个,作為當(dāng)前的headView或footView凤薛,當(dāng)我們做下拉刷新或者上拉加載時(shí),就動態(tài)的改變此ViewGroup的height诞仓,當(dāng)然缤苫,此時(shí)只能做到把當(dāng)前headView或footView拉伸或壓縮,無法改變其他的狀態(tài)(比如自己定義的箭頭圖片或者刷新圖片什么時(shí)候做出相應(yīng)的變化)墅拭,所以需要一個(gè)接口來監(jiān)聽我們做下拉刷新或上拉加載的狀態(tài)活玲,繼續(xù)往下看。

6.Swipe類

??這是一個(gè)為了滿足各種監(jiān)聽和通知實(shí)現(xiàn)的一個(gè)類谍婉,里面是各種接口舒憾,其中有兩個(gè)public接口和兩個(gè)protect接口。

/**
 * 上拉和下拉時(shí)滑動監(jiān)聽
 */
public interface OnSlideActionListener {
    /**
     * 釋放刷新行為
     */
    void releaseRefreshAction();

    /**
     * 下拉刷新行為
     */
    void downRefreshAction();

    /**
     * 釋放加載行為
     */
    void releaseLoadAction();

    /**
     * 上拉加載行為
     */
    void upLoadAction();
}

/**
 * 改變頭尾提示信息穗熬,僅限本包類
 */
interface OnChangeViewTip {
    /**
     * 改變底部view提示
     *
     * @param tips
     */
    void changeFootTips(String tips);

    /**
     * 改變頭view提示
     *
     * @param tips
     */
    void changeHeadTips(String tips);
}

/**
 * 監(jiān)聽刷新和加載更多
 */
public interface OnRefreshAndLoadListener {
    /**
     * 下拉刷新
     */
    void refresh();

    /**
     * 上拉加載
     */
    void loadMore();

}

/**
 * 監(jiān)聽頭尾view的高度變化
 */
interface OnChangeViewHeight {
    /**
     * 改變頭view高度
     *
     * @param headViewHeight
     */
    void changeHeadViewHeight(int headViewHeight);

    /**
     * 改變底部view高度
     *
     * @param footViewHeight
     */
    void changeFootViewHeight(int footViewHeight);
}
  • OnSlideActionListener:上拉和下拉時(shí)滑動監(jiān)聽镀迂,一般不用實(shí)現(xiàn),它監(jiān)聽了所有的滑動事件唤蔗,只有在自定義headView和footView時(shí)需要實(shí)現(xiàn)這個(gè)接口探遵,可以通過這個(gè)接口中的方法改變狀態(tài)窟赏。
  • OnChangeViewTip:用protect修飾,不對外開放箱季,改變提示信息涯穷,如是自定義headView或footView,直接實(shí)現(xiàn)OnSlideActionListener接口即可藏雏,所以開放此接口無意義拷况,僅由RecycleViewAdapter類和ListViewHeadAndFootManager類實(shí)現(xiàn)。
  • OnRefreshAndLoadListener:刷新或加載監(jiān)聽掘殴。
  • OnChangeViewHeight:改變headView或footView高度蝠嘉,實(shí)現(xiàn)延展樣式,用protect修飾杯巨,由RecycleViewAdapter類和ListViewHeadAndFootManager類實(shí)現(xiàn)。

??以上這些接口在SwipeRefreshLoadLayout類中都有調(diào)用的地方努酸,都非常容易理解所以不貼代碼了服爷,感興趣的可以看下整個(gè)項(xiàng)目。

7.ListViewHeadAndFootManager類

??此類和RecycleViewAdapter類功能相似获诈,由于ListView本身有setHeadView()和setFootView()方法仍源,所以不需要繼承BaseAdapter來實(shí)現(xiàn)headView和footView,里面的代碼和RecycleViewAdapter的代碼大致相同舔涎,所以就不多做介紹了笼踩。

8.SwipeLinearLayoutManager類

??繼承于LinearLayoutManager類,里面只覆寫了一個(gè)方法:

@Override
public int getDecoratedBottom(View child) {
    if (mySwipe != null && child.getId() == mySwipe.headViewId) {
        return 1 + getBottomDecorationHeight(child);
    }
    return super.getDecoratedBottom(child);
}
  • 我們在使用RecyclerView時(shí)亡嫌,禁用嵌套滑動的情況下需要使用boolean canScrollVertically(int direction)方法判斷是否滑到了最頂端或最底部嚎于,當(dāng)滑到最頂端時(shí),如果headView的height為0挟冠,那么此方法就會判斷失誤于购,無法觸發(fā)下拉刷新,具體原因可以追蹤一下源碼知染,此方法會調(diào)用一次int getDecoratedBottom(View child)方法肋僧,我們只要判斷當(dāng)child為headView時(shí)返回值大于0即可。

??以上控淡,就是本問自定義ViewGroup的全部內(nèi)容了嫌吠,具體使用方法和源碼可以在GitHub上查看:

https://github.com/chengzhicao/RefreshAndLoad

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市掺炭,隨后出現(xiàn)的幾起案子辫诅,更是在濱河造成了極大的恐慌,老刑警劉巖涧狮,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泥栖,死亡現(xiàn)場離奇詭異簇宽,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吧享,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門魏割,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钢颂,你說我怎么就攤上這事钞它。” “怎么了殊鞭?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵遭垛,是天一觀的道長。 經(jīng)常有香客問我操灿,道長锯仪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任趾盐,我火速辦了婚禮庶喜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘救鲤。我一直安慰自己久窟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布本缠。 她就那樣靜靜地躺著斥扛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丹锹。 梳的紋絲不亂的頭發(fā)上稀颁,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音楣黍,去河邊找鬼峻村。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锡凝,可吹牛的內(nèi)容都是我干的粘昨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼窜锯,長吁一口氣:“原來是場噩夢啊……” “哼张肾!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起锚扎,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤吞瞪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后驾孔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芍秆,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惯疙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妖啥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霉颠。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖荆虱,靈堂內(nèi)的尸體忽然破棺而出蒿偎,到底是詐尸還是另有隱情,我是刑警寧澤怀读,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布泥技,位于F島的核電站秒旋,受9級特大地震影響厂镇,放射性物質(zhì)發(fā)生泄漏涮较。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一啤誊、第九天 我趴在偏房一處隱蔽的房頂上張望岳瞭。 院中可真熱鬧,春花似錦坷衍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孟抗,卻和暖如春迁杨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背凄硼。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工铅协, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摊沉。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓狐史,卻偏偏與公主長得像,于是被迫代替她去往敵國和親说墨。 傳聞我的和親對象是個(gè)殘疾皇子骏全,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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