1. 程式人生 > >實現 RecyclerView 上拉載入及自動載入

實現 RecyclerView 上拉載入及自動載入

之前在《一步步打造自己的通用上拉載入佈局》(如果沒有看過,建議先看下這一篇)寫到如何實現一個通用的上拉載入佈局,本文將基於此進行擴充套件,實現 RecyclerView 的上拉載入及自動載入。

在之前的《一步步打造自己的通用上拉載入佈局》已經提到,下拉重新整理是將獲取到的資料替換掉原有的資料,而上拉載入則是將獲取到的資料插入到原來資料的末尾與底部提示載入的 View(如FooterView)之間,它們的展現方式的不同,使得上拉出來用於提示載入的 View,對於列表類檢視如 ListViewRecyclerView 而言,適合作為它們的 FooterView 而不適合封裝到我們的上拉布局中。因此,我們需要繼承 LoadMoreLayout

並實現對於 RecyclerView 的擴充套件。

準備工作

由於在 LoadMoreLayout 中已經把上拉的邏輯都封裝好,因此這裡主要是對 RecyclerViewFooterView 及自動載入的封裝。在這裡,對於帶 FooterView 的 Adapter,我是通過包裝原有的 Adapter 來實現,這樣在使用的時候就不需要把原來的 Adapter 改成我們的上拉載入中的 Adapter,以減少對程式碼的侵入。這個 Adapter 程式碼如下。這個類程式碼可不必細看,它是上拉載入的副產品,不是主要邏輯,於這裡貼出只是交底。

/*
 * Copyright (c) 2017. Xi'an iRain IOT Technology service CO., Ltd (ShenZhen). All Rights Reserved.
 */
package com.githang.recycleradapter; import android.support.v7.widget.RecyclerView; import android.view.ViewGroup; /** * @author Geek_Soledad ([email protected]) * @since 2017-05-03 0.1 */ public abstract class HiRecyclerAdapter extends RecyclerView.Adapter { /** * 由於RecyclerView的靈活性,可能會出現不同的Adapter的巢狀,而因此可能導致viewType重複。 * 因此在這裡嘗試為每個Adapter定義一個區間,而避免它們的重複問題。 */
public static final int TYPE_SECTION = 2 << 10; /** * 從2<<20開始,留足夠的type值給content使用。 */ private static final int HEADER_OFFSET = TYPE_SECTION << 10; private static final int FOOTER_OFFSET = HEADER_OFFSET + TYPE_SECTION; private int mHeaderCount; private int mContentCount; private int mFooterCount; private RecyclerView.Adapter mWrapperAdapter; private RecyclerView.AdapterDataObserver mDataObserver; public HiRecyclerAdapter(RecyclerView.Adapter wrapperAdapter) { mWrapperAdapter = wrapperAdapter; mDataObserver = new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { notifyDataSetChanged(); } @Override public void onItemRangeChanged(int positionStart, int itemCount) { notifyItemRangeChanged(positionStart + mHeaderCount, itemCount); } @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { notifyItemRangeChanged(positionStart + mHeaderCount, itemCount, payload); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { notifyItemRangeInserted(positionStart + mHeaderCount, itemCount); } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { notifyItemRangeRemoved(positionStart + mHeaderCount, itemCount); } @Override public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { notifyItemMoved(fromPosition + mHeaderCount, toPosition + mHeaderCount); } }; mWrapperAdapter.registerAdapterDataObserver(mDataObserver); } public RecyclerView.Adapter getWrapperAdapter() { return mWrapperAdapter; } public int getContentPosition(int position) { return position - mHeaderCount; } @Override public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType >= HEADER_OFFSET && viewType < HEADER_OFFSET + TYPE_SECTION) { return onCreateHeaderHolder(parent, viewType - HEADER_OFFSET); } else if (viewType >= FOOTER_OFFSET && viewType < FOOTER_OFFSET + TYPE_SECTION) { return onCreateFooterHolder(parent, viewType - FOOTER_OFFSET); } else { return mWrapperAdapter.onCreateViewHolder(parent, viewType); } } /** * Called when RecyclerView needs a new header {@link RecyclerView.ViewHolder} of the given type to * represent an item. * * @param parent The ViewGroup into which the new View will be added after it is bound to * an adapter position. * @param viewType The view type of the new View. * @return A new ViewHolder that holds a View of the given view type. */ protected abstract RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent, int viewType); /** * Called when RecyclerView needs a new footer {@link RecyclerView.ViewHolder} of the given type to * represent an item. * * @param parent The ViewGroup into which the new View will be added after it is bound to * an adapter position. * @param viewType The view type of the new View. * @return A new ViewHolder that holds a View of the given view type. */ protected abstract RecyclerView.ViewHolder onCreateFooterHolder(ViewGroup parent, int viewType); @Override public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (position < mHeaderCount) { onBindHeaderHolder(holder, position); } else if (position < mHeaderCount + mContentCount) { mWrapperAdapter.onBindViewHolder(holder, position - mHeaderCount); } else { onBindFooterHolder(holder, position - mHeaderCount - mContentCount); } } /** * Called by RecyclerView to display the header at the specified position. This method should * update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect the header * item at the given position. * * @param holder The ViewHolder which should be updated to represent the contents of the * item at the given position in the data set. * @param position The position of the item within the adapter's data set. */ protected abstract void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position); /** * Called by RecyclerView to display the footer at the specified position. This method should * update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect the footer * item at the given position. * * @param holder The ViewHolder which should be updated to represent the contents of the * item at the given position in the data set. * @param position The position of the item within the adapter's data set. */ protected abstract void onBindFooterHolder(RecyclerView.ViewHolder holder, int position); public int getHeaderCount() { return 0; } public final int getContentCount() { return mWrapperAdapter.getItemCount(); } public int getFooterCount() { return 0; } @Override public final int getItemCount() { mHeaderCount = getHeaderCount(); mContentCount = mWrapperAdapter.getItemCount(); mFooterCount = getFooterCount(); return mHeaderCount + mContentCount + mFooterCount; } @Override public final int getItemViewType(int position) { if (position < mHeaderCount) { return validateViewType(getHeaderViewType(position)) + HEADER_OFFSET; } else if (position < mHeaderCount + mContentCount) { return validateViewType(mWrapperAdapter.getItemViewType(position - mHeaderCount)); } else { return validateViewType(getFooterViewType(position - mHeaderCount - mContentCount)) + FOOTER_OFFSET; } } private int validateViewType(int viewType) { if (viewType < 0 || viewType >= TYPE_SECTION) { throw new IllegalStateException("viewType must be between 0 and " + TYPE_SECTION); } return viewType; } protected int getHeaderViewType(int position) { return 0; } protected int getFooterViewType(int position) { return 0; } public void notifyFooterInsert(int positionStart) { notifyItemInserted(mHeaderCount + mContentCount + positionStart); } public void notifyFooterRemoved(int positionStart) { notifyItemRemoved(mHeaderCount + mContentCount + positionStart); } }

接下來,繼承自這個 Adapter,實現對 FooterView 新增,程式碼如下:

/*
 * Copyright (c) 2017. Xi'an iRain IOT Technology service CO., Ltd (ShenZhen). All Rights Reserved.
 */
package com.githang.hiloadmore.recyclerview;

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

import com.githang.recycleradapter.HiRecyclerAdapter;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Geek_Soledad ([email protected])
 * @since 2017-05-03 0.1
 */
public class FooterRecyclerAdapter extends HiRecyclerAdapter {

    private List<View> mFooterViews = new ArrayList<>();

    public FooterRecyclerAdapter(RecyclerView.Adapter wrapperAdapter) {
        super(wrapperAdapter);
    }

    @Override
    protected RecyclerView.ViewHolder onCreateHeaderHolder(ViewGroup parent, int viewType) {
        return null;
    }

    @Override
    protected RecyclerView.ViewHolder onCreateFooterHolder(ViewGroup parent, int viewType) {
        return new FooterViewHolder(mFooterViews.get(viewType));
    }

    @Override
    protected void onBindHeaderHolder(RecyclerView.ViewHolder holder, int position) {
    }

    @Override
    protected void onBindFooterHolder(RecyclerView.ViewHolder holder, int position) {
    }

    @Override
    protected int getFooterViewType(int position) {
        return position;
    }

    @Override
    public int getFooterCount() {
        return mFooterViews.size();
    }

    void addFooterView(View view) {
        if (!mFooterViews.contains(view)) {
            mFooterViews.add(view);
            notifyFooterInsert(mFooterViews.indexOf(view));
        }
    }

    void removeFooterView(View view) {
        if (mFooterViews.contains(view)) {
            int index = mFooterViews.indexOf(view);
            mFooterViews.remove(view);
            notifyFooterRemoved(index);
        }
    }

    static FooterRecyclerAdapter wrapper(RecyclerView.Adapter adapter) {
        if (adapter instanceof FooterRecyclerAdapter) {
            return (FooterRecyclerAdapter) adapter;
        } else {
            return new FooterRecyclerAdapter(adapter);
        }
    }

    static class FooterViewHolder extends RecyclerView.ViewHolder {

        FooterViewHolder(View itemView) {
            super(itemView);
            RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) itemView.getLayoutParams();
            if (lp == null) {
                lp = new RecyclerView.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                itemView.setLayoutParams(lp);
            }
        }
    }
}

然後,實現一個預設的 FooterView。它的佈局檔案很簡單,就是一個 TextView,如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:id="@+id/footer_text"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:gravity="center"
    android:textColor="#666"
    android:textSize="12sp"/>

它需要實現 LoadMoreUIHandler 介面,Java 程式碼如下:

/*
 * Copyright (c) 2017. Xi'an iRain IOT Technology service CO., Ltd (ShenZhen). All Rights Reserved.
 */
package com.githang.hiloadmore.recyclerview;

import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.widget.FrameLayout;
import android.widget.TextView;

import com.githang.hiloadmore.LoadMoreUIHandler;
import com.githang.hiloadmore.R;

/**
 * @author Geek_Soledad ([email protected])
 * @since 2017-05-03 0.1
 */
public class RecyclerFooterView extends FrameLayout implements LoadMoreUIHandler {
    private TextView mText;

    public RecyclerFooterView(@NonNull Context context) {
        super(context);
        LayoutInflater.from(context).inflate(R.layout.hlm_footer_load_more, this, true);
        mText = findViewById(R.id.footer_text);
        mText.setText(R.string.hlm_pull_to_load_more);
    }

    @Override
    public void onPrepare() {
        mText.setText(R.string.hlm_pull_to_load_more);
    }

    @Override
    public void onBegin() {
        mText.setText(R.string.hlm_tip_loading_more);
    }

    @Override
    public void onComplete(boolean hasMore) {
        mText.setText(hasMore ? R.string.hlm_pull_to_load_more : R.string.hlm_tip_load_finish);
    }

    @Override
    public void onPositionChange(int offsetY, int offsetToLoadMore) {
        if (Math.abs(offsetY) > offsetToLoadMore) {
            mText.setText(R.string.hlm_release_to_load_more);
        } else {
            mText.setText(R.string.hlm_pull_to_load_more);
        }
    }
}

上拉載入的 FooterView 擴充套件

前面這些,都只是對 FooterView 顯示的準備工作。最後,才是對 LoadMoreLayout 實現支援 RecyclerView 的擴充套件。如果你不需要這個FooterView,那麼直接使用 LoadMoreLayout 也是可以的。當然,這裡的擴充套件過程也很簡單,繼承 LoadMoreLayout,然後重寫 protected void onFinishInflate() 方法,限定子 View 只能是 RecyclerView,再定義一個設定 FooterView 的方法,如此即可,程式碼如下:

/*
 * Copyright (c) 2017. Xi'an iRain IOT Technology service CO., Ltd (ShenZhen). All Rights Reserved.
 */
package com.githang.hiloadmore.recyclerview;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;

import com.githang.hiloadmore.LoadMoreLayout;

/**
 * RecyclerView的上拉載入以及自動載入實現。
 *
 * @author Geek_Soledad ([email protected])
 * @since 2017-05-03 0.1
 */
public class LoadMoreRecyclerViewContainer extends LoadMoreLayout {
    private View mFooterView;

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

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        View content = getContentView();
        if (!(content instanceof RecyclerView)) {
            throw new IllegalStateException("LoadMoreRecyclerViewContainer only support RecyclerView");
        }
    }

    public void setFooterView(final View footer) {
        final RecyclerView recyclerView = (RecyclerView) getContentView();
        final FooterRecyclerAdapter adapter = FooterRecyclerAdapter.wrapper(recyclerView.getAdapter());
        if (mFooterView != null && mFooterView != footer) {
            adapter.removeFooterView(mFooterView);
        }
        adapter.addFooterView(footer);
        mFooterView = footer;
        recyclerView.setAdapter(adapter);
        footer.post(new Runnable() {
            @Override
            public void run() {
                int height = footer.getHeight();
                if (height > 0) {
                    setOffsetYToLoadMore(height);
                }
            }
        });
    }
}

這就完成了上拉載入的擴充套件。

實現自動載入

接下來實現自動載入,這個也很簡單,思路就是監聽 RecyclerView 的滾動,如果到達底部,則主動觸發上拉載入。這裡只需要定義一個表示是否需要自動載入的成員變數,新增其 setter 方法,然後修改上面重寫的 protected void onFinishInflate() 方法,給 RecyclerView 加一個監聽器即可,如下:


    private View mFooterView;

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        View content = getContentView();
        if (content instanceof RecyclerView) {
            ((RecyclerView) content).addOnScrollListener(new RecyclerView.OnScrollListener() {

                private LinearLayoutManager mLinearLayoutManager;

                @Override
                public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                    if (!mAutoLoadMore || !hasMore()) {
                        return;
                    }
                    if (mLinearLayoutManager == null) {
                        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                        if (layoutManager instanceof LinearLayoutManager) {
                            mLinearLayoutManager = (LinearLayoutManager) layoutManager;
                        }
                    }
                    if (mLinearLayoutManager == null) {
                        return;
                    }

                    if ((mLinearLayoutManager.getItemCount() - recyclerView.getChildCount())
                            <= mLinearLayoutManager.findFirstVisibleItemPosition()) {
                        triggerToLoadMore();
                    }
                }
            });
        } else {
            throw new IllegalStateException("LoadMoreRecyclerViewContainer only support RecyclerView");
        }
    }

    public void setAutoLoadMore(boolean autoLoadMore) {
        mAutoLoadMore = autoLoadMore;
    }

這樣就擴充套件完成了。

使用示例

以上程式碼已提交至 Github 上的 hi-loadmore 專案,並且已釋出至 bintray。使用方式如下:

首先新增 Gradle 依賴:

    compile 'com.githang:hi-loadmore:0.1.1'
    // OR
    implementation 'com.githang:hi-loadmore:0.1.1'

然後在佈局中使用 LoadMoreRecyclerViewContainer作為 RecyclerView 的父佈局。接下來在 Java 程式碼中新增配置:


        RecyclerFooterView footerView = new RecyclerFooterView(this);
        mLoadMoreLayout.setFooterView(footerView);
        mLoadMoreLayout.setLoadMoreUIHandler(footerView);
        mLoadMoreLayout.setHasMore(true);

然後設定觸發了上拉載入後的處理:

        mLoadMoreLayout.setLoadMoreHandler(new LoadMoreHandler() {
            @Override
            public void onLoadMore() {
                // 在這裡請求資料
            }
        });

最後看下執行結果:
RecyclerView 上拉載入

相關推薦

實現 RecyclerView 載入自動載入

之前在《一步步打造自己的通用上拉載入佈局》(如果沒有看過,建議先看下這一篇)寫到如何實現一個通用的上拉載入佈局,本文將基於此進行擴充套件,實現 RecyclerView 的上拉載入及自動載入。 在之前的《一步步打造自己的通用上拉載入佈局》已經提到,下拉重新整理

詳解自主實現RecyclerView重新整理、載入、Header、Footer以及swiperefreshlayout的部分講解

  年前年後的那段時間比較忙,忙的來忘了寫部落格。最近空閒了,有時間了,來看部落格發現有兩個來月沒有發文章了,對自己的沒有堅持先來幾個,部落格還是要寫的,以後會持續更新。   廢話少說,網上關於列表控

SwipeToLoadLayout實現Android下重新整理載入

最近一段時間需要對手上的專案進行重構,一方面等甲方對業務流程的需求,另一方面由於新配了電腦可以流暢的執行Android Studio了正好也閒著沒事就打算把UI重新搞一搞順便降低各個模組的耦合度,在實現列表的地方噁心到我了。 因為之前一直用的的PullToRefresh在列

RecyclerView 載入更多滾動到底部的判斷()

關於下拉重新整理上拉載入更多,網上有很多例子;下拉重新整理比較簡單直接使用系統提供 SwipeRefreshLayout 即可,比較麻煩的是上拉載入更多,實現上拉的方法多種多樣,這裡對各個方法總結一下。 需求分析 RecyclerView 滾動到底部後,

RecyclerView實現重新整理與自動載入控制元件封裝

CommonAdapter.java public abstract class CommonAdapter<T> extends RecyclerView.Adapter<ViewHolder> { protected Context mContext; prot

手把手教你實現Android RecyclerView載入功能

心靈雞湯:知之者不如好之者,好之者不如樂之者。 摘要 一直在用到RecyclerView時都會微微一顫,因為一直都沒去了解怎麼實現上拉載入,受夠了每次去Github找開源引入,因為感覺就為了一個上拉載入功能而去引入一大堆你不知道有多少BUG的程式碼,不僅增加了專案的冗

集下重新整理、自動載入和側滑選單的RecyclerView基本實現原理

目錄 目錄 前言 結尾 前言   現在這個功能的框架也挺多的了。之所以要寫是因為這個框架是自己親手實現的。說起來有點小激動,這是我正經寫出來的第一個框架。對於”不要重複造輪子”這句話,我一直不是太認同,得從不同的維度看。如果從使用上

微信小程式實現列表載入,自動適應螢幕

1.檢視層wxml <view class="page__bd"> <!-- 列表渲染 --> <block wx:for="{{goodsList

RecyclerView重新整理,下載入

Activity介面方法 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_mai

拿來就用的 RecyclerView 載入

上拉載入在 RecyclerView 中應用已經十分普遍,很多封裝好的 RecyclerView 框架中也都有整合該功能,但如果我們只是想在一個頁面中實現改功能,引入一個第三方框架顯然是不划算的。 閒言少敘書歸正傳: OnLoadMoreListener.java import

優雅地為RecyclerView加上頭部、下重新整理、自動載入

一、概述 我們在寫專案的時候,永遠都離不開ListView、RecyclerView這類的控制元件,幾乎是在任何的APP中都可以看到他們的影子,但是RecyclerView並沒有像ListView提供了addHeadView、addFooterView

RecyclerView載入更多下重新整理優雅姿勢

BaseRecyclerViewAdapterHelper+EasyRefreshLayout優雅實現recyclerView上拉載入更多下拉重新整理直接上程式碼:佈局:<com.ajguan.l

RecyclerView重新整理下載入

因為請求的資料是可以分頁的,所以下拉載入時直接改變其中的一個數值,可能不是很規範。 mvp請求成功後,在MainActivity裡寫如下程式碼 在onCreate()裡面 initView(); initData(); 在initData()裡面 presenter

解決AppBarLayout 與SwipeRefreshLayout RecyclerView 載入更多導致的延遲

import android.content.Context; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout;

android快速開發框架--快速實現 頁面 載入載入失敗 無資料等狀態以及下重新整理和自動載入

RapidDevelop-Android快速開發框架 框架持續更新中 這個框架是從平時專案裡用的比較多的框架裡整合而來 對本專案感興趣的可以一起研究喜歡的朋友歡迎star 同時也歡迎大家的寶貴意見issues 如果大家對MVP模式的開發 網路爬蟲以及快取策略

XRecyclerView實現RecyclerView重新整理上來載入 自己做了部分修改,使程式碼更簡潔易用

首先感謝 開源作者 /** * Created by jianghejie on 15/11/26. */ 的無私奉獻  我是在他的基礎上做的修改 以前一直是用的XListView   可是 測試發現有一個奇奇怪怪的BUG,我們還無法解決。最嚴重的是 XListV

Ecplise中tomcat執行HTML檔案自動載入

首先說什麼熱部署 今天在做一個SpringBoot的專案時候,發現一個很煩的問題,我修改了HTML然後ecplise上面在server上面執行,但是讓人傷心的是修改的HTML檔案都都需要重啟tomcat 真的太讓人難受了,不可能這樣一直重新啟動啊 修改了半天沒有看出來怎麼解決這樣的

微信小程式-scroll-view多次觸發載入事件

之前那篇部落格裡邊寫了滑塊的margin屬性影響了滑動效果。這篇文章寫一下上拉時多次觸發上拉載入事件。 (改好的程式碼如下) 其實也比較簡單,就是有可能沒有意識到這個小細節。在官方文件中有這麼一個屬性(如圖)。這個屬性是設定距離底部多少距離是觸發事件。我們吧這個屬性設定成0就可以了。只有上拉到最

mint-ui更新時資料載入後,拉動滑回到首頁

原因見地址:https://blog.csdn.net/woyidingshijingcheng/article/details/79899823   解決方法:獲取手機螢幕高度,設定高度為100%的大小。 <template> <div id='carL

用mescroll實現無限增加數據,下刷新數據 (學習筆記)

back num desc des tar todo cti hdr index 最近自己做一個web app需要用到上拉查詢下頁數據,網上看了很多很多帖子,發現並不能快速的套用,總是會出現各種問題無法使用,於是無奈自己跑去看了官方api文檔,終於做了出來,至此做個筆記,以