之前寫過一篇使用RecyclerView祭示,一句代碼就夠了断傲,介紹了一個(gè)功能較完善的RecyclerView框架的實(shí)現(xiàn)脱吱。該框架雖然代碼不多,但是仍然不夠簡潔艳悔,耦合度也比較高急凰,難以擴(kuò)展∨觯現(xiàn)將里面的核心部分 OneAdapter 抽取出來猜年,去掉不必要的泛型、類型判斷和其他方法疾忍,以實(shí)現(xiàn)最簡單乔外、通用性和擴(kuò)展性最好的Adapter。
在Github上搜索adatper一罩,選Java語言杨幼,有5K+的記錄,主要也都是RecyclerView或ListView的適配器封裝聂渊。既然已經(jīng)有這么多實(shí)現(xiàn)在先差购,這里再實(shí)現(xiàn)一遍有意義嗎?
有的汉嗽,這里的實(shí)現(xiàn)是最簡單欲逃、代碼最少的。
OneAdapter代碼如下:
/**
* A custom adapter, supports multi-ItemViewType
*
* Created by rome753 on 2018/2/1.
*/
public class OneAdapter extends RecyclerView.Adapter<OneViewHolder> {
private final List<Object> data;
private final List<OneListener> listeners;
public OneAdapter(OneListener... listeners) {
this.data = new ArrayList<>();
this.listeners = new ArrayList<>();
this.listeners.addAll(Arrays.asList(listeners));
}
public void setData(List<?> data) {
this.data.clear();
this.data.addAll(data);
}
public void addData(List<?> data) {
this.data.addAll(data);
}
public List<Object> getData() {
return data;
}
public List<OneListener> getListeners() {
return listeners;
}
@Override
public int getItemViewType(int position) {
Object o = data.get(position);
for (int i = 0; i < listeners.size(); i++) {
OneListener listener = listeners.get(i);
if (listener.isMyItemViewType(position, o)) {
return i;
}
}
return 0;
}
@Override
public OneViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return listeners.get(viewType).getMyViewHolder(parent);
}
@Override
public void onBindViewHolder(OneViewHolder holder, int position) {
Object o = data.get(position);
holder.bindView(position, o);
}
@Override
public int getItemCount() {
return data.size();
}
}
OneAdatper繼承自RecyclerView.Adapter饼暑,重寫了4個(gè)方法稳析,并在其中增加了兩個(gè)List洗做,核心代碼只有幾行,在 getItemViewType(int position) 這個(gè)方法中彰居。
原因
很多同學(xué)開發(fā)時(shí)看到有列表就來一個(gè)RecyclerView诚纸,然后又實(shí)現(xiàn)一個(gè)Adapter。這兩步都是沒有必要的陈惰。
先說第一步畦徘,RecyclerView并不是有列表就使用的。Recycle的意思是回收奴潘,也就是說旧烧,只有在需要回收時(shí)才使用。什么時(shí)候需要回收呢画髓?列表數(shù)據(jù)項(xiàng)很多或者單個(gè)數(shù)據(jù)項(xiàng)占內(nèi)存很大時(shí)掘剪。其他情況下,比如類似微信的設(shè)置頁面那種簡單的列表奈虾,不需要回收夺谁,用ScrollView實(shí)現(xiàn)就可以了,代碼更簡單肉微,性能更好匾鸥。這應(yīng)該也是Google讓開發(fā)者從ListView遷移到RecyclerView的目的。
再說第二步碉纳,每個(gè)RecyclerView實(shí)現(xiàn)一個(gè)Adapter也是冗余的勿负。Adapter的本質(zhì)是控制列表中每一項(xiàng)的視圖(View)與數(shù)據(jù)(Data)的對(duì)應(yīng)關(guān)系,所以它應(yīng)該只做一件事:RecyclerView把某一項(xiàng)視圖傳過來時(shí)劳曹,Adapter把數(shù)據(jù)傳給視圖奴愉。然而現(xiàn)在Adapter中處理了數(shù)據(jù)類型和視圖類型,這導(dǎo)致它跟具體業(yè)務(wù)耦合度很高铁孵,尤其是數(shù)據(jù)類型和視圖類型多樣時(shí)锭硼。
舉個(gè)例子:
class MyAdapter extends BaseAdapter<SkuItem, BaseHolder<BaseView>> {
private final int TYPE_HEADER = 0;
private final int TYPE_COINS = 1;
@Override
public int getItemCount() {
return mData.size();
}
@Override
public BaseHolder<BaseView> onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
return new BaseHolder(new CoinNumberView(parent.getContext()));
} else {
return new BaseHolder(new BuyCoinsView(parent.getContext(), BuyCoinActivity.this));
}
}
@Override
public void onBindViewHolder(BaseHolder<BaseView> holder, int position) {
if (holder.itemView instanceof BuyCoinsView) {
BuyCoinsView buyCoinsView = (BuyCoinsView) holder.bindView;
buyCoinsView.bindDataByPosition(mData.get(position), position);
} else if (holder.itemView instanceof CoinNumberView) {
CoinNumberView coinNumberView = (CoinNumberView) holder.bindView;
coinNumberView.bindData(null);
}
}
@Override
public int getItemViewType(int position) {
if (position == 0) {
return TYPE_HEADER;
} else {
return TYPE_COINS;
}
}
}
這里為了給列表增加一個(gè)Header,在Adapter中增加一個(gè)類型蜕劝,然后不得不用 if...else... 或者 switch 語句判斷ItemViewType的類型檀头、ViewHolder的類型和ItemView的類型。
Adapter依賴所有類型的所有對(duì)象岖沛,畫圖來看是這樣的:
這里只是兩種類型暑始,如果有4,5種乃至7,8種,那么Adapter就爆炸了婴削!
原理
OneAdapter解決了Adapter的過度耦合問題廊镜,它只依賴OneListener和OneViewHolder這兩個(gè)類,只關(guān)聯(lián)List<OneListener>這一個(gè)對(duì)象馆蠕,其他所有依賴關(guān)系都被List<OneListener>轉(zhuǎn)移到外部了期升。如圖所示:
無論有多少種數(shù)據(jù)類型惊奇,都只需要在外部實(shí)現(xiàn)OneListener和OneViewHolder,給OneAdapter傳入OneListener列表即可播赁。
OneAdapter不依賴具體的數(shù)據(jù)類型颂郎,使用Object表示數(shù)據(jù)類型,而不是泛型容为。這樣做是因?yàn)榉盒鸵话汜槍?duì)一種或固定幾種不確定的類型乓序,而Adapter中不但有多種不確定的類型、而且具體有幾種也是不固定的坎背,因此無法使用泛型替劈。為了傳入數(shù)據(jù)不限制于Object類型,在OneAdapter中的 setData(List<?> data) 方法參數(shù)使用了泛型的不確定類型得滤。
OneListener代碼如下:
/**
* A listener for: define item view type and create ViewHolder, outside of the adapter
*/
public interface OneListener{
/**
* Is the position or the data suits for this OneListener?
* @param position the data's position int the list
* @param o the data
* @return true/false
*/
boolean isMyItemViewType(int position, Object o);
/**
* Create a ViewHolder for this OneListener
* @param parent RecyclerView
* @return OneViewHolder
*/
OneViewHolder getMyViewHolder(ViewGroup parent);
}
OneListener是一個(gè)接口陨献,它建立了列表中具體位置、具體數(shù)據(jù)與具體OneViewHolder的對(duì)應(yīng)關(guān)系懂更。實(shí)際上每個(gè)OneListener實(shí)例表示列表中一種條目類型眨业。它里面有兩個(gè)方法。
- isMyItemViewType(int position, Object o) 方法讓實(shí)現(xiàn)者根據(jù)位置或者該位置的數(shù)據(jù)判斷是不是當(dāng)前OneListener對(duì)應(yīng)的條目類型沮协。
- getMyViewHolder(ViewGroup parent) 方法讓實(shí)現(xiàn)者實(shí)現(xiàn)當(dāng)前OneListener對(duì)應(yīng)的OneViewHolder子類龄捡。
OneListener也不依賴具體的數(shù)據(jù)類型,因?yàn)榕袛鄺l目類型并不一定是根據(jù)數(shù)據(jù)類型判斷慷暂,也可能根據(jù)位置判斷聘殖。這給了調(diào)用者最大的靈活度既鞠。雖然OneViewHolder有泛型详拙,但是OneListener并不需要關(guān)心。
OneViewHolder代碼如下:
/**
* A ViewHolder that auto cast the data, from Object to the type you define
* @param <D> the data type you define
*/
public abstract class OneViewHolder<D> extends RecyclerView.ViewHolder {
public OneViewHolder(View itemView) {
super(itemView);
}
public OneViewHolder(ViewGroup parent, int layoutRes) {
super(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
}
void bindView(int position, Object o){
bindViewCasted(position, (D) o);
}
protected abstract void bindViewCasted(int position, D d);
}
OneViewHolder繼承自RecyclerView.ViewHolder庸推,它將具體數(shù)據(jù)與視圖綁定蘑辑。由于數(shù)據(jù)在OneAdapter中都是Object類型洋机,為了調(diào)用者方便坠宴,這里利用泛型自動(dòng)對(duì)數(shù)據(jù)進(jìn)行了強(qiáng)制類型轉(zhuǎn)換洋魂。至于綁定視圖封裝了兩個(gè)方法:
- OneViewHolder(ViewGroup parent, int layoutRes) 方法用于直接傳入ItemView的布局資源,用于大多數(shù)情況喜鼓。
- OneViewHolder(View itemView) 方法用于ItemView是自定義View的情況(此時(shí)要注意自定義View的LayoutParams)副砍。
到這里,主要代碼就講完了庄岖。
示例
-
簡單列表
SimpleList.png
public class SimpleListActivity extends AppCompatActivity {
OneAdapter oneAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
oneAdapter = new OneAdapter(new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return true;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder<String>(parent, R.layout.item_text){
@Override
protected void bindViewCasted(int position, String s) {
TextView text = itemView.findViewById(R.id.text);
text.setText(s);
}
};
}
});
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(oneAdapter);
requestData();
}
private void requestData() {
List<String> data = new ArrayList<>();
for(int i = 'A'; i <= 'z'; i++) {
data.add(" " + (char)i);
}
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
}
}
- 帶Header和Footer的列表
public class HeaderFooterActivity extends AppCompatActivity {
RecyclerView recyclerView;
OneAdapter oneAdapter;
View footerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
oneAdapter = new OneAdapter(
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return position == 0;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder<Object>(parent, R.layout.item_text) {
@Override
protected void bindViewCasted(int position, Object o) {
TextView text = itemView.findViewById(R.id.text);
text.setText("This is header");
}
};
}
},
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return o instanceof String;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder<String>(parent, android.R.layout.simple_list_item_1) {
@Override
protected void bindViewCasted(int position, String s) {
TextView text = itemView.findViewById(android.R.id.text1);
text.setText(s);
}
};
}
},
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return position == oneAdapter.getItemCount() - 1;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder<Object>(footerView) {
@Override
protected void bindViewCasted(int position, Object o) {
}
};
}
}
);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(oneAdapter);
initFooterView();
requestData();
}
private void initFooterView() {
footerView = LayoutInflater.from(this).inflate(R.layout.item_text, recyclerView, false);
((TextView)footerView.findViewById(R.id.text)).setText("This is footer");
}
private void requestData() {
List<Object> data = new ArrayList<>();
data.add(null);
for (int i = 'A'; i <= 'Z'; i++) {
data.add(" " + (char) i);
}
data.add(null);
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
}
}
擴(kuò)展:Databinding支持
Databinding是Google推薦的做法豁翎,有了它就不需要寫 findViewById() 語句了,還能直接在Layout文件中綁定數(shù)據(jù)隅忿。 使用方法也很簡單心剥,大家可以自己查一下相關(guān)教程邦尊。這里給OneAdapter添加Databinding支持。
對(duì)于OneAdapter來說优烧,Databinding主要用于具體數(shù)據(jù)與視圖綁定蝉揍,也就是OneViewHolder中所做的。OneViewHolder有兩個(gè)構(gòu)造方法畦娄,分別對(duì)應(yīng)自定義View和布局資源文件又沾。對(duì)于自定義View來說,是否使用Databinding是調(diào)用者自己控制的熙卡。因此Databinding支持是針對(duì)使用布局資源文件的情況杖刷,這里封裝了一個(gè)包裝類OneViewHolderWrapper,用它替換OneViewHolder即可驳癌。
OneViewHolderWrapper代碼:
/**
* A wrapper of OneViewHolder, supports data binding
* @param <D> the type of the data
* @param <B> the type of the ViewDataBinding
*/
public abstract class OneViewHolderWrapper<D,B extends ViewDataBinding>{
private OneViewHolder<D> oneViewHolder;
protected B binding;
public OneViewHolderWrapper(ViewGroup parent, int layoutRes){
binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layoutRes, parent, false);
oneViewHolder = new OneViewHolder<D>(binding.getRoot()) {
@Override
protected void bindViewCasted(int position, D d) {
OneViewHolderWrapper.this.bindViewCasted(position, d);
}
};
}
public OneViewHolder<D> getOneViewHolder() {
return oneViewHolder;
}
protected abstract void bindViewCasted(int position, D d);
}
OneViewHolderWrapper中用D表示數(shù)據(jù)泛型滑燃,B表示ViewDataBinding泛型。binding對(duì)象用于綁定具體數(shù)據(jù)颓鲜。
實(shí)際使用代碼:
public class DataBindingActivity extends AppCompatActivity {
OneAdapter oneAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
oneAdapter = new OneAdapter(new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return true;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolderWrapper<Person, ItemPersonBinding>(parent, R.layout.item_person) {
@Override
protected void bindViewCasted(int position, Person person) {
binding.setPerson(person);
binding.executePendingBindings();
}
}.getOneViewHolder();
}
});
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(oneAdapter);
requestData();
}
private void requestData() {
List<Object> data = new ArrayList<>();
for(int i = 0; i <= 10; i++) {
data.add(new Person("Bill", 22));
data.add(new Person("Chris", 10));
data.add(new Person("David", 36));
}
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
}
}
需要OneViewHolder實(shí)例時(shí)不瓶,先創(chuàng)建包裝類OneViewHolderWrapper實(shí)例,然后調(diào)用 getOneViewHolder() 方法從包裝類中取得OneViewHolder實(shí)例灾杰。這樣原有的OneAdapter和OneListener都直接兼容蚊丐。
擴(kuò)展:下拉刷新和加載更多
用SwipeRefreshLayout和FooterView實(shí)現(xiàn)了簡單的下拉刷新和加載更多功能,這是對(duì)OneAdapter的簡單擴(kuò)展艳吠。沒有加入EmptyView麦备,因?yàn)镋mptyView可以完全在外部控制。
public class RecyclerLayout extends SwipeRefreshLayout implements OnRefreshListener, LoadingLayout.OnLoadingListener {
private RecyclerView recyclerView;
private LoadingLayout loadingLayout;
private OneAdapter oneAdapter;
private GridLayoutManager gridLayoutManager;
private OnRefreshListener onRefreshListener;
private LoadingLayout.OnLoadingListener onLoadingListener;
public RecyclerLayout(Context context) {
this(context, null);
}
public RecyclerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setOnRefreshListener(this);
loadingLayout = new LoadingLayout(context);
gridLayoutManager = new GridLayoutManager(context, 1);
recyclerView = new RecyclerView(context);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
int lastVisibleItemPosition;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
lastVisibleItemPosition = gridLayoutManager.findLastVisibleItemPosition();
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == oneAdapter.getItemCount() - 1 - 1) {
// load more
onLoading();
}
}
});
addView(recyclerView);
}
public void init(final OneAdapter oneAdapter, OnRefreshListener onRefreshListener, LoadingLayout.OnLoadingListener onLoadingListener){
this.recyclerView.setAdapter(oneAdapter);
this.oneAdapter = oneAdapter;
this.oneAdapter.getListeners().add(0, new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return position == oneAdapter.getItemCount() - 1;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder(loadingLayout) {
@Override
protected void bindViewCasted(int position, Object o) {
//ignore
}
};
}
});
if(onRefreshListener == null){
setEnabled(false);
}
this.onRefreshListener = onRefreshListener;
this.onLoadingListener = onLoadingListener;
}
@Override
public void onRefresh() {
if(onRefreshListener != null){
onRefreshListener.onRefresh();
}
}
@Override
public void onLoading() {
if(onLoadingListener != null && !isRefreshing() && !isLoading() && !isNoMore()){
onLoadingListener.onLoading();
setLoading(true, isNoMore());
}
}
public void setData(List<?> data, boolean hasMore){
data.add(null);
oneAdapter.setData(data);
oneAdapter.notifyDataSetChanged();
setRefreshing(false);
setLoading(false, !hasMore);
}
public void addData(List<?> data, boolean hasMore){
List<Object> cur = oneAdapter.getData();
if(!cur.isEmpty()){
cur.remove(cur.size() - 1);
}
data.add(null);
oneAdapter.addData(data);
oneAdapter.notifyDataSetChanged();
setLoading(false, !hasMore);
}
private boolean isNoMore(){
return loadingLayout.isNoMore();
}
private boolean isLoading(){
return loadingLayout.isLoading();
}
private void setLoading(boolean loading, boolean isNoMore){
loadingLayout.setLoading(loading, isNoMore);
}
}
實(shí)際使用如下:
public class RefreshActivity extends AppCompatActivity {
RecyclerLayout recyclerLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recyclerLayout = new RecyclerLayout(this);
setContentView(recyclerLayout);
OneAdapter oneAdapter = new OneAdapter(
new OneListener() {
@Override
public boolean isMyItemViewType(int position, Object o) {
return true;
}
@Override
public OneViewHolder getMyViewHolder(ViewGroup parent) {
return new OneViewHolder<String>(parent, R.layout.item_text) {
@Override
protected void bindViewCasted(int position, String s) {
TextView text = itemView.findViewById(R.id.text);
text.setText(s);
}
};
}
}
);
recyclerLayout.init(oneAdapter,
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
requestData();
}
},
new LoadingLayout.OnLoadingListener() {
@Override
public void onLoading() {
requestMoreData();
}
}
);
recyclerLayout.setRefreshing(true);
requestData();
}
int page;
private void requestData() {
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
List<Object> data = new ArrayList<>();
for (int i = 'A'; i <= 'Z'; i++) {
String s = (char) i + " " + System.nanoTime();
data.add(s);
}
page = 0;
recyclerLayout.setData(data, page++ < 2);
}
}, 1000);
}
private void requestMoreData() {
getWindow().getDecorView().postDelayed(new Runnable() {
@Override
public void run() {
List<Object> data = new ArrayList<>();
for (int i = 'A'; i <= 'Z'; i++) {
String s = (char) i + " " + System.nanoTime();
data.add(s);
}
recyclerLayout.addData(data, page++ < 2);
}
}, 1000);
}
}
代碼結(jié)構(gòu)
- 實(shí)現(xiàn)普通或多種類型的RecyclerView昭娩,使用base包中的類即可凛篙;
- 如果需要Databinding支持,加入databinding包中的類栏渺;
- 如果需要下拉刷新和加載更多呛梆,可以參考refresh包中的實(shí)現(xiàn)。
Github地址:https://github.com/rome753/OneAdapter
完整代碼和Demo示例都在這里磕诊,歡迎Fork和Star哦填物。