Android RecycleView輕松實現(xiàn)下拉刷新蒲凶、加載更多

![PullRefresh.gif](http://upload-images.jianshu.io/upload_images/2244299-ea8bb3b32f34f5aa.gif?imageMogr2/auto-orient/strip)

那如同這個題目,這里面涉及的東西其實還是比較多的缝驳,RecycleView SwipeRefreshLayout连锯,下拉刷新(這個就是SwipeRefreshLayout的),加載更多用狱。

SwipeRefreshLayout

這個是Google自己封裝的一個下拉刷新的控件运怖,里面使用了5.0開始的嵌套滑動機制,有興趣的朋友可以去看看源碼夏伊!使用起來其實就涉及到以下方法:

setOnRefreshListener() 下拉刷新的相關(guān)回調(diào)摇展。

setRefresh() 通知是否開始刷新或者刷新完成。(坑1)

setColorSchemeColors() loading的時候progressbar的顏色,支持多個溺忧。

SwipeRefreshLayout的坑

進入頁面調(diào)用setRefresh(true),根本不顯示刷新的小圓圈咏连?!
簡單的說鲁森,這個就是在onCreate()方法執(zhí)行的時候祟滴,view還沒有繪制出來,這個時候你設(shè)置刷新不刷新其實都一樣的歌溉,解決方法垄懂,post一下!

mRecyclerView.post(new Runnable() {
        @Override
        public void run() {
            mRefreshLayout.setRefreshing(refresh);
        }
    });

RecycleView

RecycleView其實出現(xiàn)都有一定的年頭了痛垛,前幾天公司來面試的居然說他還沒有用過草慧。。這個也是醉醉的榜晦!

RecycleViewListView的強力升級冠蒋!加入了holder便于管理和復(fù)用相同的類型。

就我目前掌握的情況乾胶,RecycleView對于ListView有了以下的不同:

1抖剿、加入了LayoutManager用用管理各種類型的布局,而且通過不同的布局可以實現(xiàn)橫向识窿、豎向、瀑布式的等各種復(fù)雜的布局喻频。

2缩宜、加入Holder來管理相關(guān)布局和復(fù)用,對于每一種Type的View你都要創(chuàng)建一個對應(yīng)的Holder來管理它!

3锻煌、取消了header和bottom布局妓布。

4、沒有現(xiàn)成的itemClick回調(diào)宋梧。

5匣沼、引入了豐富的動畫效果。(坑4)

6捂龄、添加了豐富的數(shù)據(jù)刷新的方法释涛,可以局部刷新了!(坑3)

7倦沧、可自定義相關(guān)分割線唇撬。

8、支持swipe刪除和drag排序展融。(ItemTouchHelper 幫助類)

9窖认、默認是不顯示scrollBar的(坑2)

10、可以設(shè)置不同類型holder占據(jù)不同的空間(ItemColumnSpan GridLayoutManager)

上面這些不是所有的都講愈污,其實本文主要涉及的就是相關(guān)adapter耀态,里面對應(yīng)不同的holder轮傍,及相關(guān)的封裝暂雹。然后說說踩了哪些坑。

基本思路

  • 1创夜、明確什么時候開始加載更多?

下拉刷新就調(diào)用SwipeRefreshLayout相關(guān)就好了杭跪,那么加載更多呢?這個就要自己去寫相關(guān)的布局了驰吓。然后第一個問題涧尿,什么時候加載更多侮东?谍憔?因為RecycleView有各種布局桑嘶,所以判斷最后一個也是要區(qū)分不同的adapter的曙砂!

  • 2娩梨、加載更多有多少種情況打肝?
    大致有三種铅辞,正在加載更多谋国;加載更多錯誤葵礼;沒有更多數(shù)據(jù)了;

具體實現(xiàn)

1号阿、監(jiān)聽滑動,滿足條件開始加載更多鸳粉。

        @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        if (null != scrollListener) {
            scrollListener.onScrolled(SwipeRefreshRecycleView.this, dx, dy);
        }
        if (null == manager) {
            throw new RuntimeException("you should call setLayoutManager() first!!");
        }
        if (null == adapter) {
            throw new RuntimeException("you should call setAdapter() first!!");
        }
        if (manager instanceof LinearLayoutManager) {
            int lastCompletelyVisibleItemPosition = ((LinearLayoutManager) manager).findLastCompletelyVisibleItemPosition();

            if (adapter.getItemCount() > 1 && lastCompletelyVisibleItemPosition >= adapter.getItemCount() - 1 && adapter.isHasMore()) {
                adapter.isLoadingMore();
                if (null != listener) {
                    listener.onLoadMore();
                }
            }
            int position = ((LinearLayoutManager) manager).findFirstVisibleItemPosition();
            if (lastTitlePos == position) {
                return;
            }
            lastTitlePos = position;
        }
        if (manager instanceof StaggeredGridLayoutManager) {
            int[] itemPositions = new int[2];
            ((StaggeredGridLayoutManager) manager).findLastVisibleItemPositions(itemPositions);

            int lastVisibleItemPosition = (itemPositions[1] != 0) ? ++itemPositions[1] : ++itemPositions[0];

            if (lastVisibleItemPosition >= adapter.getItemCount()  && adapter.isHasMore()) {
                adapter.isLoadingMore();
                if (null != listener) {
                    listener.onLoadMore();
                }
            }

        }

    }

2扔涧、定義自己的加載更多的ViewHolder。

3.定義相關(guān)的方法實時更新ViewHolder的三種狀態(tài)。

public class NewBottomViewHolder extends RecyclerView.ViewHolder{
    @Bind(R.id.footer_container)
    public LinearLayout contaier;

    @Bind(R.id.progressbar)
    ProgressBar pb;
    @Bind(R.id.content)
    TextView content;
    @Nullable
    private final SwipeRefreshRecycleView.OnRefreshLoadMoreListener mListener;

    public NewBottomViewHolder(View itemView, SwipeRefreshRecycleView.OnRefreshLoadMoreListener listener) {

        super(itemView);
        ButterKnife.bind(this,itemView);
        mListener = listener;
    }

    public void bindDateView(int state) {
        switch (state) {
            case AdapterLoader.STATE_LASTED:
                contaier.setVisibility(View.VISIBLE);
                contaier.setOnClickListener(null);
                pb.setVisibility(View.GONE);
                content.setText("---  沒有更多了  ---");
                break;
            case AdapterLoader.STATE_LOADING:
                contaier.setVisibility(View.VISIBLE);
                content.setText("加載更多?菀埂弯汰!");
                contaier.setOnClickListener(null);
                pb.setVisibility(View.VISIBLE);
                break;
            case AdapterLoader.STATE_ERROR:
                contaier.setVisibility(View.VISIBLE);
                pb.setVisibility(View.GONE);
                content.setText("--- 加載更多失敗點擊重試 ---");
                contaier.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        if (mListener != null) {
                            mListener.onLoadMore();
                        }
                        content.setText("加載更多!湖雹!");
                        pb.setVisibility(View.VISIBLE);
                    }
                });
                break;
        }
    }

}

4.定義相關(guān)擴展方法便于用戶自己定義底部布局及相關(guān)狀態(tài)處理蝙泼。

這里就必須詳細講講Adapter里面的相關(guān)方法了!

getItemCount()劝枣,在RecycleView知道它一共有多少數(shù)量的Item需要展示汤踏,返回0之后不會執(zhí)行剩余的方法!

onCreateViewHolder(ViewGroup parent, int viewType),某種Type的Holder第一次創(chuàng)建的時候會調(diào)用該方法舔腾,當然沒有復(fù)用的時候也會去創(chuàng)建溪胶,一旦復(fù)用了,改方法不會再執(zhí)行了稳诚!

onBindViewHolder(RecyclerView.ViewHolder holder, int position),每一次更新對應(yīng)itemView的時候都會調(diào)用該方法哗脖,所以在該方法中要實時的刷新數(shù)據(jù)!(因為存在復(fù)用扳还,所以刷新的時候一定要徹底2疟堋!氨距!

以上三個方法是必須實現(xiàn)的桑逝,因為在父類adapter里是抽象滴!

還有一個方法也比較重要:

getItemViewType(int position),這個方法是返回對應(yīng)pos的類型的俏让,如果你只有一個類型楞遏,不需要重寫該方法,默認返回的是0首昔。

是不是這么說起來比ListView還要爽一點兒寡喝?不用去判斷什么convertView==null!

@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    switch (viewType) {
        case TYPE_BOTTOM:
            if (loadMore != null) {
                RecyclerView.ViewHolder holder = onBottomViewHolderCreate(loadMore);
                if (holder == null) {
                    throw new RuntimeException("You must impl onBottomViewHolderCreate() and return your own holder ");
                }
                return holder;
            } else {
                return new BottomViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_footer, parent, false));
            }
        default:
            return onViewHolderCreate(parent, viewType);
    }

}

@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (getItemViewType(position) == TYPE_BOTTOM) {
        loadState = loadState == STATE_ERROR ? STATE_ERROR : isHasMore() ? STATE_LOADING : STATE_LASTED;
        if (loadMore != null) {
            try {
                onBottomViewHolderBind(holder, loadState);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            try {
                ((BottomViewHolder) holder).bindDateView(loadState);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    } else {
        onViewHolderBind(holder, position);
    }
}

這里在RefreshRecycleAdapter<T>中實現(xiàn)了剛剛說的三個方法,并且相關(guān)的已經(jīng)加final修飾了勒奇!所以之后你只需要實現(xiàn)如下方法來完成你自己的item填充就好了:

   RecyclerView.ViewHolder onViewHolderCreate(ViewGroup parent, int viewType);

void onViewHolderBind(RecyclerView.ViewHolder holder, int position);

對于加載更多的幾種狀態(tài)的更改预鬓,提供如下的相關(guān)方法!

boolean isHasMore();

void isLoadingMore();

void loadMoreError();

對于創(chuàng)建自己制定的加載更多的布局赊颠,提供如下方法擴展格二!

void setLoadMoreView(View view);

RecyclerView.ViewHolder onBottomViewHolderCreate(View loadMore);

void onBottomViewHolderBind(RecyclerView.ViewHolder holder, int loadState);

還沒有說的那就是數(shù)據(jù)源相關(guān)的方法。提供了set和append兩種方式巨税!

void setList(List<T> data);

void appendList(List<T> data);

@Override
public final void appendList(List<T> data) {
    int positionStart = list.size();
    list.addAll(data);
    int itemCount = list.size() - positionStart;

    if (positionStart == 0) {
        notifyDataSetChanged();
    } else {
        notifyItemRangeInserted(positionStart + 1, itemCount);
    }
}

還是那話蟋定,這些方法都是RefreshRecycleAdapter<T>里面寫好的,我們寫自己的adapter時更本不用去care!只需要去調(diào)用setList()或者appendList()就好了2萏怼驶兜!

說到這里不得不提提RecycleView刷新數(shù)據(jù)的相關(guān)方法和坑!

notifyDataSetChanged()的基礎(chǔ)上, RecycleView增加了一系列的方法用于增刪改抄淑。所以不要再任性的一味使用notifyDataSetChanged()屠凶,這樣也不專業(yè)了!

notifyItemInserted();
notifyItemRangeInserted();

notifyItemChanged();
notifyItemRangeChanged();

notifyItemRemoved();
notifyItemRangeRemoved();

在使用的過程中肆资,發(fā)現(xiàn)調(diào)用notifyItemChanged()之后不會去執(zhí)行onBindViewHolder()矗愧,(坑3 坑4)這個就導致刷新沒有觸發(fā)了!最后搜到的結(jié)果是因為mRecyclerView.setItemAnimator(new DefaultItemAnimator())引起的郑原,解決方案是復(fù)寫相關(guān)方法

@Override
public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull List<Object> payloads) {
    return true;
}

小結(jié)

首先通過addList()或者appendList()的方法設(shè)置相關(guān)數(shù)據(jù)源唉韭。

滑動過程中需要加載更多時回調(diào)相關(guān)方法,并在adapter中通知相關(guān)狀態(tài)刷新犯犁。

 adapter.isLoadingMore();
 if (null != listener) {
    listener.onLoadMore();
 }

加載錯誤的時候調(diào)用相關(guān)的方法通知狀態(tài)改變属愤。

adapter.loadMoreError();

創(chuàng)建BottomHolder的時候判斷有沒有設(shè)置自定義的view,如果有,那么就去走子類的onBottomViewHolderCreate()方法創(chuàng)建自定義的Bottomholder,然后實時更新相關(guān)數(shù)據(jù)酸役!

    @Override
public final void setLoadMoreView(@NonNull View view) {
    loadMore = view;
}

if (loadMore != null) {
    RecyclerView.ViewHolder holder = onBottomViewHolderCreate(loadMore);
    if (holder == null) {
        throw new RuntimeException("You must impl onBottomViewHolderCreate() and return your own holder ");
    }
    return holder;
        } 

最后在onBottomViewHolderBind(RecyclerView.ViewHolder holder, int state)的方法中執(zhí)行bindDateView(state)實時刷新相關(guān)的狀態(tài)住诸。

public void bindDateView(int state) {
    switch (state) {
        case AdapterLoader.STATE_LASTED:
            contaier.setVisibility(View.VISIBLE);
            contaier.setOnClickListener(null);
            pb.setVisibility(View.GONE);
            content.setText("---  沒有更多了  ---");
            break;
        case AdapterLoader.STATE_LOADING:
            contaier.setVisibility(View.VISIBLE);
            content.setText("加載更多!涣澡!");
            contaier.setOnClickListener(null);
            pb.setVisibility(View.VISIBLE);
            break;
        case AdapterLoader.STATE_ERROR:
            contaier.setVisibility(View.VISIBLE);
            pb.setVisibility(View.GONE);
            content.setText("--- 加載更多失敗點擊重試 ---");
            contaier.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mListener != null) {
                        mListener.onLoadMore();
                    }
                    content.setText("加載更多<拧!");
                    pb.setVisibility(View.VISIBLE);
                }
            });
            break;
    }
}

PS 最后還有默認是不顯示scrollBar的問題入桂,這個問題奄薇,似乎必須在xml里面配置,不能代碼直接new RecycleView事格。然后可以使用相關(guān)代碼控制ScrollBar是否顯示惕艳!

mRecyclerView.setVerticalScrollBarEnabled(true)

<android.support.v7.widget.RecyclerView
    android:id="@+id/recycle_view"
    android:scrollbars="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

另外對于特性8、10這里就不詳細介紹了驹愚,滑動刪除和拖拽排序在TouchHelperCallback中有相關(guān)支持!方法如下:

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
    if (callBack != null) {
        callBack.onItemMove(viewHolder.getAdapterPosition(),
                target.getAdapterPosition());
    }
    return true;
}

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    if (callBack != null) {
        callBack.onItemDismiss(viewHolder.getAdapterPosition());
    }
}

詳細的可以參照相關(guān)Demo-FangShiActivity


gradle快速集成

  compile 'com.lovejjfg.powerrecycle:powerrecycle:1.0.0'

相關(guān)下載

演示Demo下載

項目中的使用

---- Edit By Joe ----

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末劣纲,一起剝皮案震驚了整個濱河市逢捺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌癞季,老刑警劉巖劫瞳,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绷柒,居然都是意外死亡志于,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門废睦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伺绽,“玉大人,你說我怎么就攤上這事∧斡Γ” “怎么了澜掩?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長杖挣。 經(jīng)常有香客問我肩榕,道長,這世上最難降的妖魔是什么惩妇? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任株汉,我火速辦了婚禮,結(jié)果婚禮上歌殃,老公的妹妹穿的比我還像新娘郎逃。我一直安慰自己,他們只是感情好挺份,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布褒翰。 她就那樣靜靜地躺著,像睡著了一般匀泊。 火紅的嫁衣襯著肌膚如雪优训。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天各聘,我揣著相機與錄音揣非,去河邊找鬼。 笑死躲因,一個胖子當著我的面吹牛早敬,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播大脉,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搞监,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了镰矿?” 一聲冷哼從身側(cè)響起琐驴,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秤标,沒想到半個月后绝淡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡苍姜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年牢酵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衙猪。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡馍乙,死狀恐怖布近,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情潘拨,我是刑警寧澤吊输,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站铁追,受9級特大地震影響季蚂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜琅束,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一扭屁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涩禀,春花似錦料滥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屿岂,卻和暖如春践宴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爷怀。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工阻肩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人运授。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓烤惊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吁朦。 傳聞我的和親對象是個殘疾皇子柒室,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

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