RecyclerView的下拉刷新和自動加載更多

RecyclerView是android v7包下的一個新的控件,用來顯示列表型的數據。在Recyclerview之前,我們現實縱向的列表數據時一般都是使用ListView,而用GridView來顯示網格型的列表數據。而在RecyclerView中,官方把視圖、適配器和數據集合到了一起,Adapter和ViewHolder成爲了Recyclerview的內部類。Recyclerview只負責數據的回收,不關心數據的顯示方法,把數據的顯示方法交給了LayoutManager去管理,強制使用ViewHolder。因此使用Recyclerview時寫法規範了很多。

SwipeRefreshLayout是android v4包下一個刷新的控件,不過該控件只提供了下拉刷新的功能,今天,爲大家帶來使用SwipeRefreshLayout+Recyclerview來實現Recyclerview的下拉刷新和自動加載更多。

首先看一下例子的最終效果圖:


我們要做的是在下拉時刷新數據集(DataSet)並且把新的數據加在Recyclerview的最上面,當往上拖動Recyclerview時,當到達數據集的最後一個item時,自動加載更多的數據(而不是像下拉刷新要拖動一段距離來觸發)。

實現的思路:使用SwipeRefreshLayout來實現下拉刷新功能,Recyclerview本身實現自動加載更多的功能。

首先是主佈局文件:activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        app:popupTheme="@style/AppTheme.PopupOverlay" />

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swiperefresh"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recyclerview"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

主佈局非常簡單,只有一個toolbar和一個SwipeRefreshLayout,而Recyclerview嵌套在SwipeRefreshLayout當中。

到這裏,我們先想一下Recyclerview所需要的幾樣東西:Recyclerview、DataSet、Adapter。

現在Recyclerview已經寫好了,DataSet是在調用者中獲得,並且傳入到Adapter中的,這一點和ListView是一樣。

接着就是我們的Adapter:MyAdapter.java

等等,在寫適配器之前,我們需要一個實體,這個實體是和Recyclerview中的一個item是相對應的,在該例子中,實體很簡單,只有一張圖片和一段文字,新建一個Item.java:

public class Item {

    public int image;
    public String text;

}

這裏爲了方便所以直接使用圖片資源的ID了,實際項目中,很可能是從本地讀取獲取網絡讀取的,那麼這個圖片應該用URI或者Bitmap,這個要根據具體來設定。然後就是我們的適配器了,誒等等!!示例圖中最後那個loading是什麼鬼?好像和item不一樣?沒錯,這個是Recyclerview的尾部,主要是用來提示用戶我們的應該正在爲您加載數據,您先等一下,尾部一般會用一個正在加載的圈圈或者自定義的動畫來實現,這裏爲了方便我直接使用了一段文字,所以我們在res/layout下創建一個footer.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="loading..."
        android:gravity="center_horizontal"
        android:textSize="20sp"
        android:textStyle="bold"
        />

</LinearLayout>

這個很簡單,就不多說了。

 

接着,真的到了我們最核心的Adapter了:創建一個MyAdapter.java並且繼承RecyclerView.Adapter,支持泛型(用來傳遞我們的實體)

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static int TYPE_NORMAL = 0;
    private static int TYPE_FOOTER = 1;

    private LayoutInflater mInflater;

    private Context mContext;

    private List<Item> mDatas;

    public MyAdapter(Context context, List<Item> data) {
        this.mContext = context;
        this.mDatas = data;
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        if(TYPE_NORMAL == viewType) {
            View view = mInflater.inflate(R.layout.item, parent, false);
            MyViewHolder holder = new MyViewHolder(view);
            return holder;
        } else if(TYPE_FOOTER == viewType) {
            View view = mInflater.inflate(R.layout.footer, parent, false);
            FooterViewHolder holder = new FooterViewHolder(view);
            return holder;
        } else {
            return null;
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        if(holder instanceof MyViewHolder) {
            ((MyViewHolder)holder).imageView.setImageResource(mDatas.get(position).image);
            ((MyViewHolder)holder).textView.setText(mDatas.get(position).text);
        }

    }


    @Override
    public int getItemViewType(int position) {

        if(position + 1 == getItemCount()) {
            return TYPE_FOOTER;
        } else {
            return TYPE_NORMAL;
        }
    }

    @Override
    public int getItemCount() {
        return mDatas.size() + 1;
    }
}

class MyViewHolder extends RecyclerView.ViewHolder {

    public ImageView imageView;
    public TextView textView;

    public MyViewHolder(View itemView) {
        super(itemView);

        imageView = (ImageView) itemView.findViewById(R.id.imageView);
        textView = (TextView) itemView.findViewById(R.id.text);
    }
}

class FooterViewHolder extends RecyclerView.ViewHolder {

    public TextView footer;

    public FooterViewHolder(View itemView) {
        super(itemView);

        footer = (TextView) itemView.findViewById(R.id.footer);
    }
}

這裏有3個類,第一個就是我們定義的適配器,後兩個是ViewHolder。

自定義適配器裏聲明瞭幾個成員,重寫了一些方法,下面簡單地說明一下這些成員和方法的作用:

TYPE_NORMAL和TYPE_FOOTER:其實就是用來區分Recyclerview中的不同item的,因爲我們的Recyclerview除了顯示真實的數據item之外,還有一個用來表示加載中的footer。

LayoutManager、Context和List<Item>和我們在使用ListView是一樣的,這裏不再多說了。

然後是幾個重寫的方法:

onCreateViewHolder(ViewGroup parent, intviewType):創建View。因爲設備在繪製Recyclerview時是一個item一個item來畫的,所以第一次繪製時需要調用這個方法。這裏我們注意到第二個參數viewType,因爲Recyclerview是可以混合不同類型的view的,本例子中有帶數據是item和一個footer,所以我們在方法中進行了判斷。在該方法中主要的工作是把item的佈局文件轉換成一個view。

onBindViewHolder(RecyclerView.ViewHolderholder, int position):綁定ViewHolder。在該方法中主要的工作是給item中具體的控件設置值。

getItemViewType(int position):獲取item的類型。本例子有兩種類型的View,帶數據的item和一個footer。如果我們的Recyclerview中只用一種View,可以不重寫該方法。

getItemCount():獲取Recyclerview中item的個數,和ListView一致。本例子中有一個footer,所以返回數據集的個數加1。

下面的兩個ViewHolder就是給Item中的具體控件賦值。

最後回到我們的MainActivity:

public class MainActivity extends AppCompatActivity {

    private List<Item> data = new ArrayList<>();
    private int lastVisibleItem;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        initData();

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        final MyAdapter adapter = new MyAdapter(this, data);
        final LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);

        final SwipeRefreshLayout srl = (SwipeRefreshLayout) findViewById(R.id.swiperefresh);
        srl.setColorSchemeColors(Color.BLUE);
        srl.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                Item item = new Item();
                item.image = R.drawable.user;
                item.text = "新增一個item";
                data.add(0, item);



                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                        srl.setRefreshing(false);
                    }
                }, 2000);

            }
        });



        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView,
                                             int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                //RecyclerView沒有拖動而且已經到達了最後一個item,執行自動加載
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 == adapter.getItemCount()) {

                    for(int i = 0; i < 3; i++) {
                        Item item = new Item();
                        item.image = R.drawable.user;
                        item.text = String.valueOf(Integer.parseInt(data.get(lastVisibleItem - 1).text) + 1);
                        data.add(item);
                    }

                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            adapter.notifyDataSetChanged();
                        }
                    }, 1500);

                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                lastVisibleItem = manager.findLastVisibleItemPosition();
            }

        });
        recyclerView.setHasFixedSize(true);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(manager);


    }

    private void initData() {
        for(int i = 1; i <= 6; i++) {
            Item item = new Item();
            item.image = R.drawable.user;
            item.text = String.valueOf(i);

            data.add(item);
        }
    }
}

在activity中,主要做的工作有三個:給我們的SwipeRefreshLayout添加刷新事件、給Recyclerview添加觸摸事件、給Recyclerview綁定適配器。

1、  setOnRefreshListener方法接收一個SwipeRefreshLayout.OnRefreshListener的接口,重寫它的onRefresh方法,在此我們在數據集的前面添加“新增一個item”的Item,並且調用Recyclerview.Adapter.notifyDataSetChanged()方法讓通知適配器我們的數據集發生了變化。最後記得調用SwipeRefreshLayout.setRefreshing(false)來讓刷新動作停止,否則SwipeRefreshLayout會一直在刷新,即使我們的數據已經刷新完畢了,它還會像嗑了炫邁一樣根本停不下來!!!另外值得一提的是SwipeRefreshLayout有一個setColorSchemeResources(int... colorResIds)方法用來設置刷新時的圈圈的顏色變化,就是圈圈每轉一圈就換一種顏色,最多可傳入4中顏色,可以設置資源文件的顏色(R.color.xx),也可以像例子中一樣直接用android.graphics.Color下的預定義的顏色,甚至可以傳入顏色的16進制整數(比如0FFF0000FF)。本例子只設置了一種顏色。

2、  使用Recyclerview.addOnScrollListener(newRecyclerView.OnScrollListener())方法給Recyclerview添加滾動事件來實現自動加載更多的功能。我們在onScrolled方法中通過Recyclerview的佈局管理器LinearLayoutManager(本例子使用線性佈局)獲取最後一個可見item的位置值(如無意外就是我們的footer的位置),然後在onScrollStateChanged方法中進行判斷——當在不滾動(newState == RecyclerView.SCROLL_STATE_IDLE)時且可見item爲數據集中最後一個(lastVisibleItem+ 1 == adapter.getItemCount())時,就加載新的數據。最後再通知適配器我們的數據集發生了變化。

3、  最後是綁定適配器並設置佈局管理器到Recyclerview上。


在本文的最後總結一下實現的步驟:

1、  準備好佈局:SwipeRefreshLayout、Recyclerview、footer等

2、  準備好需要顯示的item的實體類

3、  實現我們的Adapter

4、  在調用者中初始化各個控件,並且給SwipeRefreshLayout設置刷新動作,給Recyclerview添加滾動事件、綁定適配器和佈局管理器


最後值得一提的是知乎和知乎日報都是使用SwipeRefreshLayout來實現的,只是它們的自動加載更多沒有footer而已。







發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章