前言
RecyclerView出來(lái)有好幾年了湃缎,它的重要性不言而喻。然而RecyclerView只提供了基本的View復(fù)用功能蠢壹,相關(guān)功能如刷新嗓违、點(diǎn)擊等都需要開發(fā)者自己實(shí)現(xiàn),每個(gè)項(xiàng)目實(shí)現(xiàn)一遍RecyclerView功能集成又無(wú)必要图贸,因此出現(xiàn)了許多RecyclerView封裝的“輪子”蹂季,Github上一搜多如牛毛。
簡(jiǎn)介
輪子雖多疏日,各有特點(diǎn)偿洁。有時(shí)候還是自己造的最適合,OneRecyclerView這個(gè)輪子的特點(diǎn)如下:
- 用很少的代碼(主要Java代碼300多行)實(shí)現(xiàn)了RecyclerView集成的大部分功能沟优,包括下拉刷新涕滋、加載更多、多種ViewType净神、多列顯示何吝、自定義HeaderView和空數(shù)據(jù)EmptyView
- 實(shí)現(xiàn)多種ViewType的方式很巧妙,不需要復(fù)雜的映射關(guān)系鹃唯,不需要注冊(cè)類型,不需要反射
- 一句代碼即可調(diào)用瓣喊,多種ViewType也是如此
效果圖
使用方法
- 在布局文件中添加OneRecyclerView
<?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">
<cc.rome753.demo.onerecycler.OneRecyclerView
android:id="@+id/orv"
android:layout_width="match_parent"
android:layout_height="match_parent">
</cc.rome753.demo.onerecycler.OneRecyclerView>
</LinearLayout>
- 實(shí)現(xiàn)自定義ViewHolder
class UserInfoVH extends OneVH<UserInfo> {
public UserInfoVH(ViewGroup parent) {//1.設(shè)置item布局文件
super(parent, R.layout.item_user_simple);
}
@Override
public void bindView(int position, final UserInfo o) {//2.處理點(diǎn)擊事件和設(shè)置數(shù)據(jù)
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), o.getName(), Toast.LENGTH_SHORT).show();
}
});
TextView tvName = itemView.findViewById(R.id.tv_name);
tvName.setText(o.getName());
}
}
包括item的布局文件坡慌、給item設(shè)置數(shù)據(jù)和點(diǎn)擊事件
- 一句代碼使用OneRecyclerView
mOneRecyclerView.init(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestData(false);
}
},
new OneLoadingLayout.OnLoadMoreListener() {
@Override
public void onLoadMore() {
requestData(true);
}
},
new OnCreateVHListener() {
@Override
public OneVH onCreateHolder(ViewGroup parent) {
return new UserInfoVH(parent);
}
@Override
public boolean isCreate(int position, Object o) {
return position % 3 > 0;
}
}
);
調(diào)用OneRecyclerView的init()方法,傳入下拉刷新監(jiān)聽藻三、加載更多監(jiān)聽和創(chuàng)建ViewHolder監(jiān)聽即可洪橘。
OneRecyclerView的init()方法最后一個(gè)參數(shù)是可變參數(shù),針對(duì)多種ItemType情況:
實(shí)現(xiàn)多個(gè)ViewHolder棵帽,用OnCreateVHListener包裝并傳入
- 添加自定義header
View header = View.inflate(this, R.layout.layout_header, null);
mOneRecyclerView.addHeader(header);
可添加多個(gè)header熄求,多次調(diào)用addHader()方法即可,header的顯示完全由自身控制
- 設(shè)置多列顯示的列數(shù)
mOneRecyclerView.setSpanCount(3);
不設(shè)置默認(rèn)是1列逗概;多列顯示與多種ViewType一般不會(huì)同時(shí)用到弟晚,根據(jù)具體需求選擇其一
原理分析
下拉刷新
使用官方的SwipeRefreshLayout
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srl_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
布局文件中使用SwipeRefreshLayout,在里面放入RecyclerView逾苫,然后setOnRefreshListener設(shè)置刷新監(jiān)聽
加載更多
給RecyclerView.Adapter的ItemCount + 1
卿城,并使用一個(gè)單獨(dú)的ViewType。當(dāng)劃到底部時(shí)铅搓,顯示一個(gè)loading布局瑟押;獲取到數(shù)據(jù)后再隱藏這個(gè)布局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ProgressBar
android:layout_gravity="center"
android:layout_width="30dp"
android:layout_height="48dp" />
</FrameLayout>
這里只放了一個(gè)ProgressBar,可以自定義
ViewHolder封裝
對(duì)于一個(gè)通用的庫(kù)來(lái)說(shuō):
- 外部調(diào)用者使用的數(shù)據(jù)類型
- RecyclerView的Item布局
- Item加載數(shù)據(jù)的方式
- Item點(diǎn)擊事件的處理
這幾點(diǎn)都是不確定的星掰,第一點(diǎn)需要使用泛型多望,即由調(diào)用者定義數(shù)據(jù)類型嫩舟;后面三點(diǎn)都可以在ViewHolder中處理。這里封裝一個(gè)繼承自RecyclerView.ViewHolder
的抽象類OneVH<T>
怀偷,具體由調(diào)用者實(shí)現(xiàn)
public abstract class OneVH<T> extends RecyclerView.ViewHolder {
public OneVH(View itemView) {
super(itemView);
}
public OneVH(ViewGroup parent, int layoutRes) {
super(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
}
public abstract void bindView(int position, T t);
}
一個(gè)簡(jiǎn)單的OneVH<T>
實(shí)現(xiàn)類如下
class TextVH extends OneVH<UserInfo> {
public TextVH(ViewGroup parent) {//1.設(shè)置item布局文件
super(parent, android.R.layout.simple_list_item_1);
}
@Override
public void bindView(int position, final UserInfo o) {//2.處理點(diǎn)擊事件和設(shè)置數(shù)據(jù)
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), o.getName(), Toast.LENGTH_SHORT).show();
}
});
TextView tvName = itemView.findViewById(android.R.id.text1);
tvName.setText(o.getName());
tvName.setBackgroundColor(Color.GREEN);
}
}
OnCreateVHListener封裝
封裝好ViewHolder之后至壤,并不是直接創(chuàng)建ViewHolder對(duì)象傳入Adapter的,因?yàn)锳dapter中的onCreateViewHolder方法創(chuàng)建ViewHolder的時(shí)機(jī)和數(shù)量是不確定的枢纠,所以需要定義一個(gè)接口OnCreateVHListener
public interface OnCreateVHListener<S extends OneVH, T>{
/**
* 創(chuàng)建ViewHolder
* @param parent RecyclerView
* @return S extends OneVH
*/
S onCreateHolder(ViewGroup parent);
}
Adapter中需要?jiǎng)?chuàng)建ViewHolder時(shí)像街,調(diào)用OnCreateVHListener的onCreateHolder方法,返回一個(gè)自定義的OneVH實(shí)現(xiàn)對(duì)象
@Override
public S onCreateViewHolder(ViewGroup parent, int viewType) {
...
return onCreateVHListener.onCreateHolder(parent);
}
因?yàn)镺nCreateVHListener能返回具體OneVH<T>實(shí)現(xiàn)對(duì)象晋渺,所以Adapter只依賴OnCreateVHListener镰绎,不依賴OneVH。也就是說(shuō)外部調(diào)用者只需要傳一個(gè)OnCreateVHListener實(shí)現(xiàn)類給OneRecyclerView就行(OneRecyclerView傳給Adapter)
經(jīng)過(guò)以上步驟木西,已經(jīng)能很方便地使用單一ViewType的RecyclerView了畴栖。調(diào)用者只要實(shí)現(xiàn)自己的ViewHolder,不需要關(guān)心其他八千。下面介紹在此基礎(chǔ)上多種ViewType的實(shí)現(xiàn)
實(shí)現(xiàn)多種ViewType
首先分析一下ViewType的原理憨降。Adapter中跟ViewType相關(guān)的主要是getItemViewType()
和onCreateViewHolder()
方法
@Override
public int getItemViewType(int position) {
}
@Override
public S onCreateViewHolder(ViewGroup parent, int viewType) {
}
getItemViewType()
方法用于確定當(dāng)前位置的item屬于哪種類型,返回一個(gè)表示類型viewType的int值
onCreateViewHolder()
方法則是根據(jù)類型viewType獲取對(duì)應(yīng)的ViewHolder
常規(guī)思路思考玛界,這里需要記錄兩種對(duì)應(yīng)關(guān)系:
- 位置position與viewType的對(duì)應(yīng)關(guān)系
- viewType與ViewHolder的對(duì)應(yīng)關(guān)系
那就需要兩個(gè)Map來(lái)保存急鳄,也許再加一個(gè)類管理它們。能否將這兩個(gè)Map合并成一個(gè)沸停?或者根本不用Map膜毁?
先考慮第二個(gè)對(duì)應(yīng)關(guān)系,由于前面已經(jīng)將ViewHolder類型綁定到OnCreateVHListener接口上愤钾,不同的viewType也就對(duì)應(yīng)了不同的OnCreateVHListener對(duì)象瘟滨。比如有3種Item類型,那么就有3個(gè)OnCreateVHListener對(duì)象能颁,這3個(gè)viewType用3個(gè)不同int值分別對(duì)應(yīng)杂瘸。
其實(shí)用Map是一種冗余,3個(gè)或更多int值完全可以用0,1,2...
表示伙菊,那么3個(gè)OnCreateVHListener可以直接用List<OnCreateVHListener>
保存败玉,每個(gè)OnCreateVHListener在List中的序號(hào)就是它的viewType!
接著考慮第一個(gè)對(duì)應(yīng)關(guān)系占业,根據(jù)position獲取viewType值绒怨,在有了List<OnCreateVHListener>
的基礎(chǔ)上,viewType就是OnCreateVHListener在List中的序號(hào)谦疾。直接獲取這個(gè)序號(hào)并不好實(shí)現(xiàn)南蹂,能否先獲取OnCreateVHListener,再遍歷List<OnCreateVHListener>
獲得它的位置念恍?
根據(jù)position獲取OnCreateVHListener也不方便六剥,這個(gè)對(duì)應(yīng)關(guān)系是調(diào)用者定義的晚顷,需要給外部提供一種很自然的定義方式,而不是注冊(cè)類型或定義一個(gè)Manager類疗疟。其實(shí)可以不用考慮對(duì)應(yīng)關(guān)系该默,每個(gè)OnCreateVHListener只需要知道對(duì)應(yīng)的position是不是自己就行了。
這樣在OnCreateVHListener接口中添加一個(gè)isCreate(int position, T t)
方法策彤,參數(shù)是位置position和對(duì)應(yīng)位置的數(shù)據(jù)栓袖,調(diào)用者通過(guò)這兩個(gè)參數(shù)判斷該位置是不是對(duì)應(yīng)的ViewHolder
public interface OnCreateVHListener<S extends OneVH, T>{
/**
* 創(chuàng)建ViewHolder
* @param parent RecyclerView
* @return S extends OneVH
*/
S onCreateHolder(ViewGroup parent);
/**
* 根據(jù)當(dāng)前位置或數(shù)據(jù)判斷是否創(chuàng)建S類型的ViewHolder
* @param position
* @param t
* @return
*/
boolean isCreate(int position, T t);
}
在Adapter的getItemViewType方法中遍歷List<OnCreateVHListener>
,調(diào)用isCreate()
方法店诗,如果結(jié)果是true裹刮,就返回當(dāng)前序號(hào),這個(gè)序號(hào)就是viewType
@Override
public int getItemViewType(int position) {
...
int pos = position - headerVHList.size();
T t = data.get(pos);
for(int i = 0; i < listeners.size(); i++){
OnCreateVHListener<S,T> listener = listeners.get(i);
if(listener.isCreate(pos, t)){
return i;
}
}
return TYPE_NORMAL_MIN;
}
最終庞瘸,position捧弃、viewType、ViewHolder擦囊、OnCreateVHListener就全部關(guān)聯(lián)起來(lái)了违霞。代價(jià)只是在Adapter中添加一個(gè)List<OnCreateVHListener>
(初始化時(shí)傳進(jìn)來(lái))、OnCreateVHListener接口中添加一個(gè)方法瞬场。
雖然在getItemViewType方法里進(jìn)行了遍歷操作买鸽,但是考慮到99%的列表Item類型是個(gè)位數(shù),而且判斷類型不是耗時(shí)操作泌类,帶來(lái)的性能影響可以忽略不計(jì)
OnCreateVHListener里面定義的兩個(gè)方法癞谒,不僅關(guān)聯(lián)了ViewHolder類型,還關(guān)聯(lián)了與position的對(duì)應(yīng)關(guān)系刃榨。外部調(diào)用者使用幾種Item類型,傳入幾個(gè)OnCreateVHListener實(shí)現(xiàn)類就行了双仍。實(shí)際使用如下
mOneRecyclerView.init(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestData(false);
}
},
new OneLoadingLayout.OnLoadMoreListener() {
@Override
public void onLoadMore() {
requestData(true);
}
},
new OnCreateVHListener() {
@Override
public OneVH onCreateHolder(ViewGroup parent) {
return new UserInfoVH(parent);
}
@Override
public boolean isCreate(int position, Object o) {
return position % 3 > 0;
}
},
new OnCreateVHListener() {
@Override
public OneVH onCreateHolder(ViewGroup parent) {
return new TextVH(parent);
}
@Override
public boolean isCreate(int position, Object o) {
return position % 3 == 0;
}
}
);
OneRecyclerView.init()
方法前面兩個(gè)參數(shù)分別是下拉刷新和加載更多的回調(diào)枢希,后面兩個(gè)參數(shù)給OneRecyclerView的初始化方法傳入了兩個(gè)OnCreateVHListener,分別對(duì)應(yīng)兩個(gè)OneVH子類:UserInfoVH和TextVH朱沃。前者的isCreate()
在position % 3 > 0
時(shí)返回true苞轿,后者的isCreate()
在position % 3 == 0
時(shí)返回true。也就是位置0,3,6,9...
顯示TextVH對(duì)應(yīng)的布局逗物,位置1,2,4,5,7,8...
顯示UserInfoVH對(duì)應(yīng)的布局搬卒。這是一種交替顯示的效果(如最上面圖二orv_types.gif所示)。
總結(jié)
說(shuō)了這么多翎卓,其實(shí)實(shí)現(xiàn)代碼并不復(fù)雜契邀,只用到常見的繼承、封裝失暴、多態(tài)坯门、接口微饥、抽象類、泛型古戴,數(shù)據(jù)結(jié)構(gòu)只用到List欠橘。原因一方面是軟件設(shè)計(jì)的高級(jí)技術(shù)自己還有待學(xué)習(xí);另一方面现恼,的確肃续,實(shí)現(xiàn)一個(gè)具備常見功能、簡(jiǎn)單易用的RecyclerView小框架叉袍,這些就夠了始锚。自己實(shí)現(xiàn)一遍或者研究一遍代碼會(huì)對(duì)RecyclerView的原理和的Java基礎(chǔ)技術(shù)有較好的理解。
Github地址如下畦韭,歡迎fork
和star