前言
前面一篇文章介紹了對于RecyclerView 的擴展和封裝的一個庫,幫助我們在開發中可以快速添加一個列表,提高開發效率。還沒有看過的同學可以在看完本篇文章之后移步前一篇文章RecyclerView Adapter 優雅封裝,一個Adapter搞定所有列表,源碼請看GithubCustomAdapter。本篇文章講的就是Adapter 簡化的思路和過程。
背景
Android 開發中,我們碰到最多的就是列表了,一個APP中有簡單的列表,也有包含很多種Item的復雜列表。大多數的App首頁都是比較復雜的,比如一個社交APP的首頁,包含Banner區、廣告區、文本內容、圖片內容、視頻內容等等。RecyclerView 可以用ViewType 來區分不同的item,也可以滿足需求 ,但還是存在一些問題,比如:1,在item過多邏輯復雜列表界面,Adapter里面的代碼量龐大,邏輯復雜,后期難以維護。2,每次增加一個列表都需要增加一個Adapter,重復搬磚,效率低下。那么本篇文章講的就是從這兩個維度去簡化我們的Adapter。
思路分析與實現過程
上面提出了兩個問題,接下來看一下我們怎么去解決這兩個問題。首先我們來看一下我們常規的寫一個包含多item的復雜的列表界面(比如就是前面說的包含banner,廣告,文本內容,圖片內容,視頻內容的首頁):
/**
* Created by zhouwei on 17/2/17.
*/
public class HomePageAdapter extends RecyclerView.Adapter {
public static final int TYPE_BANNER = 0;
public static final int TYPE_AD = 1;
public static final int TYPE_TEXT = 2;
public static final int TYPE_IMAGE = 3;
public static final int TYPE_VIDEO = 4;
private List<HomePageEntry> mData;
public void setData(List<HomePageEntry> data) {
mData = data;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType){
case TYPE_BANNER:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
case TYPE_AD:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_ad_item_layout,null));
case TYPE_TEXT:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_text_item_layout,null));
case TYPE_IMAGE:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_image_item_layout,null));
case TYPE_VIDEO:
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_video_item_layout,null));
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int type = getItemViewType(position);
switch (type){
case TYPE_BANNER:
// banner 邏輯處理
break;
case TYPE_AD:
// 廣告邏輯處理
break;
case TYPE_TEXT:
// 文本邏輯處理
break;
case TYPE_IMAGE:
//圖片邏輯處理
break;
case TYPE_VIDEO:
//視頻邏輯處理
break;
// ... 此處省去N行代碼
}
}
@Override
public int getItemViewType(int position) {
if(position == 0){
return TYPE_BANNER;//banner在開頭
}else {
return mData.get(position).type;//type 的值為TYPE_AD,TYPE_IMAGE,TYPE_AD,TYPE_VIDEO其中一個
}
}
@Override
public int getItemCount() {
return mData == null ? 0:mData.size();
}
public static class BannerViewHolder extends RecyclerView.ViewHolder{
public BannerViewHolder(View itemView) {
super(itemView);
//綁定控件
}
}
public static class VideoViewHolder extends RecyclerView.ViewHolder{
public VideoViewHolder(View itemView) {
super(itemView);
//綁定控件
}
}
public static class AdViewHolder extends RecyclerView.ViewHolder{
public AdViewHolder(View itemView) {
super(itemView);
//綁定控件
}
}
public static class TextViewHolder extends RecyclerView.ViewHolder{
public TextViewHolder(View itemView) {
super(itemView);
//綁定控件
}
}
public static class ImageViewHolder extends RecyclerView.ViewHolder{
public ImageViewHolder(View itemView) {
super(itemView);
//綁定控件
}
}
}
上面這樣就是我們通常寫一個多Item列表的方法,根據不同的ViewType 處理不同的item,如果邏輯復雜,這個類的代碼量是很龐大的。如果版本迭代添加新的需求,修改代碼很麻煩,后期維護困難。
** 其實可以看到,代碼量在于每種item的視圖綁定、數據綁定和邏輯處理,并且這些操作都是Item共有的操作,既然是共有的,那么我們就可以抽取出來。因此我們可以將Item抽象成為一個獨立的組件(我們將這個組件叫做Cell),它負責每個item的視圖綁定、數據綁定和邏輯處理,現在我們就可以把Adapter 中的代碼放到Cell 中去了。Adapter 綁定視圖和綁定數據的操作調用對應位置的Cell的方法就可以了。**
那我們來看一下Cell,Cell作為一個獨立組件,它有以下功能:
1,視圖綁定
2,數據綁定
3,資源釋放
4,獨立id(讓Adapter 區分是那種Cell)
1,首先我們定義一個頂層接口Cell,對應上面的4個功能,有4個方法
/**
* Created by zhouwei on 17/1/19.
*/
public interface Cell {
/**
* 回收資源
*
*/
public void releaseResource();
/**
* 獲取viewType
* @return
*/
public int getItemType();
/**
* 創建ViewHolder
* @param parent
* @param viewType
* @return
*/
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
/**
* 數據綁定
* @param holder
* @param position
*/
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position);
}
2,既然可以綁定數據,我們要給它數據源,因此定義一個基類RVBaseCell,保存一個范型數據實體
public abstract class RVBaseCell<T> implements Cell {
public RVBaseCell(T t){
mData = t;
}
public T mData;
@Override
public void releaseResource() {
// do nothing
// 如果有需要回收的資源,子類自己實現
}
}
**3,將原來Adapter 中的視圖綁定、數據綁定、邏輯處理等操作,放到對應的Cell 中去做 **
例如,我們為Banner創建一個Cell ,叫BannerCell。代碼如下:
/**
* Created by zhouwei on 17/2/17.
*/
public class BannerCell extends RVBaseCell<HomePageEntry>{
public static final int TYPE_BANNER = 0;
public BannerCell(HomePageEntry homePageEntry) {
super(homePageEntry);
}
@Override
public int getItemType() {
return TYPE_BANNER;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new BannerViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.home_banner_layout,null));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
//處理banner 邏輯
}
public static class BannerViewHolder extends RecyclerView.ViewHolder{
public BannerViewHolder(View itemView) {
super(itemView);
//綁定控件
}
}
}
** 4,改造Adapter,Adapter中保存的不再是實體數據,而是一個Cell列表,改造Adapter 回調方法,調用對應位置的Cell中的方法。**
代碼如下:
public class HomePageAdapter<C extends RVBaseCell> extends RecyclerView.Adapter {
private List<C> mData;
public void setData(List<C> data) {
mData = data;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
for(int i=0;i<getItemCount();i++){
if(viewType == mData.get(i).getItemType()){
return mData.get(i).onCreateViewHolder(parent,viewType);
}
}
throw new RuntimeException("wrong viewType");
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
}
@Override
public int getItemViewType(int position) {
return mData.get(position).getItemType();
}
@Override
public int getItemCount() {
return mData == null ? 0:mData.size();
}
}
現在看一下這個HomePageAdapter 的代碼和之前寫的 HomePageAdapter的代碼,可以把一個上千行代碼的Adapter 縮減為只有幾十行代碼,并且更重要的是,這個Adapter 是一個通用的,我們所有的列表只需要這么一個Adapter 就OK了,只需要添加Cell就行。這樣就把我們文章開始提到的兩個問題都解決了。
我們可以用兩個圖來對比一下,以前添加一個列表就要寫一個Adapter:
現在是這樣的:
如上圖所示,只需要一個Adapter就行, 就像是Adapter上面有很多插槽,我們將一個個Cell插到Adapter上,即插即用。完全解耦,以后添加需求和砍掉需求,只需要增加一種Cell 或者減少一種Cell就行,不用動以前的老代碼,維護方便。
最后
以上就是RecyclerView Adapter 的簡化封裝過程與思路分析,文章中的代碼只放了部分,詳細的封裝代碼請看GithubCustomAdapter。CustomAdapter也集成了更多的功能,歡迎start。