一種優雅的方式實現RecyclerView條目多型別
下面以一個故事來講來說明這中方式是怎麼實現的。
放棄vlayout
大家都瞭解一般首頁是非常複雜的,去年初專案引入vlayout來解決首頁複雜佈局的問題,後來對vlayout和databinding結合進行了封裝,使用起來更方便簡單,不僅首頁使用,很多頁面都在用,還封裝了單純列表樣式的Activity,重新整理載入的Activity,這樣很開心的過了很久。由於vlayout專案一直比較活躍,在滿足各種各樣的需求上一直在打補丁,我也是一直在把它更新為最新版本,直到又一次更新我的的列表不顯示內容了,經過一上午的排查,找到了問題。是在合併一個同學的PR時引入的,當時我還提了個issue 升級後出現onBindViewHolder未分發的問題
在一次需求中,PM提出了可以刪除列表中某一條目的需求,在之前封裝的基礎上很簡單就實現了。這時想加一個移除的動畫吧,讓APP活潑點,不是那麼生硬。這可難住了我,一上午硬是沒搞出來,在別的同學的issue 怎麼正確的使用notifyItemRemoved,正是這個問題,使得我有了放棄使用vlayout的想法。不禁問自己,我為什麼要使用它,沒錯就是為了使複雜佈局更方便管理,現在看來有悖於初衷。也許vlayout有刪除動畫的簡單實現方式,而我沒有找到,但是我決定不再使用它。
尋找輪子
放棄之後面臨的另一個問題是需求還是要做,專案還是要按時上線,冒出了第一個想法是找找其他的輪子吧,
-
// 初始化adapter
-
BaseItemAdapter adapter = new BaseItemAdapter();
-
// 為TextBean資料來源註冊ViewHolderManager管理類
-
adapter.register(TextBean.class, new TextViewManager());
-
// 為更多資料來源註冊ViewHolderManager管理類
-
adapter.register(ImageTextBean.class, new ImageAndTextManager());
-
adapter.register(ImageBean.class, new ImageViewManager());
-
// 為RecyclerView設定Adapter
-
recyclerView.setAdapter(adapter);
我要做的就是快速拿它匹配下我的場景,能不能滿足我的需求。
- 是否支援多種型別條目?廢話肯定支援;
- 是否支援不同條目不同資料型別?人家就是很久需要處理的資料型別來進行選擇的,肯定沒問題;
- 能不能支援一個數據型別對應多個樣式?我看作者也是支援的,即通過資料實體中的標誌來判斷使用哪個Adapter;
- 能不能支援一個數據實體對應多個樣式?由於是基於資料的型別進行選擇代理Adapter的,這看來是無法實現。
我的微笑還沒收場,就尷尬的定住了,5秒鐘後,晃過神來,為什麼我不按照這種思想自己封裝一個。說實話我對作者的代理Adapter的管理還是不太滿意的,這種思想很好,還是忍不住給作者點贊,那就自己來擼個輪子吧。
需求迭代
簡單列表
PM在一次迭代過程中提出了要加一個新聞列表的需求,很簡單,就是左邊一個圖片,右邊一個標題、來源、釋出時間,點選可以檢視詳情。
你一看,心中默唸so easy,三下五除二,你就用RecyclerView很快實現了。
activity xml layout
-
<?xml version="1.0" encoding="utf-8"?>
-
<android.support.constraint.ConstraintLayout 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"
-
tools:context="com.kevin.myapplication.MainActivity">
-
<android.support.v7.widget.RecyclerView
-
android:id="@+id/recycler_view"
-
android:layout_width="match_parent"
-
android:layout_height="match_parent"
-
app:layout_constraintBottom_toBottomOf="parent"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toTopOf="parent" />
-
</android.support.constraint.ConstraintLayout>
實體物件
-
public class News {
-
public String imgUrl = "";
-
public String content = "";
-
public String source = "";
-
public String time = "";
-
public String link = "";
-
}
adapter
-
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> {
-
private List<News> dataItems = new ArrayList<>();
-
public void setDataItems(List<News> dataItems) {
-
this.dataItems = dataItems;
-
notifyDataSetChanged();
-
}
-
@Override
-
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false);
-
ViewHolder holder = new ViewHolder(view);
-
return holder;
-
}
-
@Override
-
public void onBindViewHolder(ViewHolder holder, int position) {
-
News news = dataItems.get(position);
-
holder.tvContent.setText(news.content);
-
holder.tvSource.setText(news.source);
-
holder.tvTime.setText(news.time);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrl).into(holder.ivPic);
-
}
-
@Override
-
public int getItemCount() {
-
return dataItems.size();
-
}
-
static class ViewHolder extends RecyclerView.ViewHolder {
-
ImageView ivPic;
-
TextView tvContent;
-
TextView tvSource;
-
TextView tvTime;
-
public ViewHolder(View view) {
-
super(view);
-
ivPic = view.findViewById(R.id.iv_pic);
-
tvContent = view.findViewById(R.id.tv_content);
-
tvSource = view.findViewById(R.id.tv_source);
-
tvTime = view.findViewById(R.id.tv_time);
-
}
-
}
-
}
adapter item layout
-
<?xml version="1.0" encoding="utf-8"?>
-
<android.support.constraint.ConstraintLayout 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="wrap_content"
-
android:paddingLeft="10dp"
-
android:paddingRight="10dp"
-
android:paddingTop="10dp">
-
<ImageView
-
android:id="@+id/iv_pic"
-
android:layout_width="0dp"
-
android:layout_height="80dp"
-
android:scaleType="centerCrop"
-
app:layout_constraintHorizontal_weight="1"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toLeftOf="@+id/tv_content"
-
app:layout_constraintTop_toTopOf="parent"
-
tools:src="@mipmap/ic_launcher" />
-
<TextView
-
android:id="@+id/tv_content"
-
android:layout_width="0dp"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="10dp"
-
android:ellipsize="end"
-
android:maxLines="2"
-
android:textColor="#333333"
-
android:textSize="18sp"
-
app:layout_constraintHorizontal_chainStyle="spread"
-
app:layout_constraintHorizontal_weight="2"
-
app:layout_constraintLeft_toRightOf="@+id/iv_pic"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toTopOf="parent"
-
tools:text="這是一條新聞,這是一條新聞,這是一條新聞" />
-
<TextView
-
android:id="@+id/tv_source"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="10dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintBottom_toBottomOf="@+id/iv_pic"
-
app:layout_constraintLeft_toRightOf="@+id/iv_pic"
-
tools:text="澎湃新聞" />
-
<TextView
-
android:id="@+id/tv_time"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="8dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintBottom_toBottomOf="@+id/iv_pic"
-
app:layout_constraintLeft_toRightOf="@+id/tv_source"
-
tools:text="07:33" />
-
<View
-
android:layout_width="0dp"
-
android:layout_height="1px"
-
android:layout_margin="10dp"
-
android:background="#EEEEEE"
-
app:layout_constraintTop_toBottomOf="@id/iv_pic" />
-
</android.support.constraint.ConstraintLayout>
看一下實現,還不錯的樣子。
擴充樣式
某天產品經理找到你說,只有一張圖片的看著太單調了,能不能擴充出另外一種樣式,一張圖片的時候還是原來的樣子,如果三張圖片的時候上面是標題,下面是圖片。你覺得沒什麼,也比較好實現,就沒有做任何反抗去做了。
應該是這樣,在之前一個圖片的ViewHolder基礎上擴充套件一個三個圖片的ViewHoder,通過實體物件的圖片數量進行區分是選擇哪一個ViewHolder,不同的ViewHolder繫結不同的資料。
修改實體物件
把原來的String型別的圖片資料改為List<String>的集合。
-
public class News {
-
public List<String> imgUrls = null;
-
public String content = "";
-
public String source = "";
-
public String time = "";
-
public String link = "";
-
}
新樣式的xml layout
-
<?xml version="1.0" encoding="utf-8"?>
-
<android.support.constraint.ConstraintLayout 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="wrap_content"
-
android:paddingLeft="10dp"
-
android:paddingRight="10dp"
-
android:paddingTop="10dp">
-
<TextView
-
android:id="@+id/tv_content"
-
android:layout_width="0dp"
-
android:layout_height="wrap_content"
-
android:ellipsize="end"
-
android:maxLines="2"
-
android:textColor="#333333"
-
android:textSize="18sp"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toTopOf="parent"
-
tools:text="這是一條新聞,這是一條新聞,這是一條新聞" />
-
<ImageView
-
android:id="@+id/iv_pic1"
-
android:layout_width="0dp"
-
android:layout_height="80dp"
-
android:layout_marginTop="10dp"
-
android:scaleType="centerCrop"
-
app:layout_constraintHorizontal_weight="1"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toLeftOf="@+id/iv_pic2"
-
app:layout_constraintTop_toBottomOf="@+id/tv_content"
-
tools:src="@mipmap/ic_launcher" />
-
<ImageView
-
android:id="@+id/iv_pic2"
-
android:layout_width="0dp"
-
android:layout_height="80dp"
-
android:layout_marginLeft="4dp"
-
android:layout_marginTop="10dp"
-
android:scaleType="centerCrop"
-
app:layout_constraintHorizontal_weight="1"
-
app:layout_constraintLeft_toRightOf="@+id/iv_pic1"
-
app:layout_constraintRight_toLeftOf="@+id/iv_pic3"
-
app:layout_constraintTop_toBottomOf="@+id/tv_content"
-
tools:src="@mipmap/ic_launcher" />
-
<ImageView
-
android:id="@+id/iv_pic3"
-
android:layout_width="0dp"
-
android:layout_height="80dp"
-
android:layout_marginLeft="4dp"
-
android:layout_marginTop="10dp"
-
android:scaleType="centerCrop"
-
app:layout_constraintHorizontal_weight="1"
-
app:layout_constraintLeft_toRightOf="@+id/iv_pic2"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toBottomOf="@+id/tv_content"
-
tools:src="@mipmap/ic_launcher" />
-
<TextView
-
android:id="@+id/tv_source"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginTop="10dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintTop_toBottomOf="@+id/iv_pic1"
-
tools:text="澎湃新聞" />
-
<TextView
-
android:id="@+id/tv_time"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="8dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintLeft_toRightOf="@+id/tv_source"
-
app:layout_constraintTop_toTopOf="@+id/tv_source"
-
tools:text="07:33" />
-
<View
-
android:layout_width="0dp"
-
android:layout_height="1px"
-
android:layout_margin="10dp"
-
android:background="#EEEEEE"
-
app:layout_constraintTop_toBottomOf="@id/tv_source" />
-
</android.support.constraint.ConstraintLayout>
Adapter改造
複寫getItemViewType方法,通過判斷圖片的個數是不是3個來區分樣式。如果是則ViewType為1,如果不是則ViewType為0。
-
@Override
-
public int getItemViewType(int position) {
-
News news = dataItems.get(position);
-
boolean isThreePic = news.imgUrls.size() == 3;
-
int viewType = isThreePic ? 1 : 0;
-
return viewType;
-
}
增加三張圖片樣式的ViewHolder
-
static class ThreePicViewHolder extends RecyclerView.ViewHolder {
-
ImageView ivPic1;
-
ImageView ivPic2;
-
ImageView ivPic3;
-
TextView tvContent;
-
TextView tvSource;
-
TextView tvTime;
-
public ThreePicViewHolder(View view) {
-
super(view);
-
ivPic1 = view.findViewById(R.id.iv_pic1);
-
ivPic2 = view.findViewById(R.id.iv_pic2);
-
ivPic3 = view.findViewById(R.id.iv_pic3);
-
tvContent = view.findViewById(R.id.tv_content);
-
tvSource = view.findViewById(R.id.tv_source);
-
tvTime = view.findViewById(R.id.tv_time);
-
}
-
}
在onCreateViewHolder中,通過判斷ViewType的型別,建立對應的ViewHolder。
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
if (viewType == 0) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news, parent, false);
-
OnePicViewHolder holder = new OnePicViewHolder(view);
-
return holder;
-
} else {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_extend, parent, false);
-
ThreePicViewHolder holder = new ThreePicViewHolder(view);
-
return holder;
-
}
-
}
在onBindingViewHolder中,通過判斷ViewType的型別,繫結對應的資料到控制元件。
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-
int viewType = holder.getItemViewType();
-
if (viewType == 0) {
-
OnePicViewHolder viewHolder = (OnePicViewHolder) holder;
-
News news = dataItems.get(position);
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);
-
} else {
-
ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;
-
News news = dataItems.get(position);
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);
-
}
-
}
OK,很快就完成了,這次的需求,看了下效果,還可以。但是沒有第一次那麼開心了,隱隱感覺這裡的程式碼有點噁心。
又擴充樣式
過了一段時間,產品又找到你,說想增加一種圖集型別的樣式,上面是標題,下面是一張大圖,點開是可以左右滑動翻頁的圖集。說完一張呆萌臉問你是不是很簡單,你想到又要加一種型別的ViewHolder,而之前的實現已經讓你不爽。你說:倒是不復雜,但是你也不能無限制的加啊。抱怨完了,還是要做的。
新增xml佈局
-
<?xml version="1.0" encoding="utf-8"?>
-
<android.support.constraint.ConstraintLayout 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="wrap_content"
-
android:paddingLeft="10dp"
-
android:paddingRight="10dp"
-
android:paddingTop="10dp">
-
<TextView
-
android:id="@+id/tv_content"
-
android:layout_width="0dp"
-
android:layout_height="wrap_content"
-
android:ellipsize="end"
-
android:maxLines="2"
-
android:textColor="#333333"
-
android:textSize="18sp"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toTopOf="parent"
-
tools:text="這是一條新聞,這是一條新聞,這是一條新聞" />
-
<ImageView
-
android:id="@+id/iv_pic"
-
android:layout_width="0dp"
-
android:layout_height="0dp"
-
android:layout_marginTop="10dp"
-
android:scaleType="centerCrop"
-
app:layout_constraintDimensionRatio="5:3"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toBottomOf="@+id/tv_content"
-
tools:src="@mipmap/ic_launcher" />
-
<TextView
-
android:id="@+id/tv_count"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:drawableLeft="@mipmap/icon_img"
-
android:drawablePadding="8dp"
-
android:paddingBottom="6dp"
-
android:paddingRight="10dp"
-
android:textColor="@android:color/white"
-
app:layout_constraintBottom_toBottomOf="@+id/iv_pic"
-
app:layout_constraintRight_toRightOf="@+id/iv_pic"
-
tools:text="圖3" />
-
<TextView
-
android:id="@+id/tv_source"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginTop="10dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintTop_toBottomOf="@+id/iv_pic"
-
tools:text="澎湃新聞" />
-
<TextView
-
android:id="@+id/tv_time"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="8dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintLeft_toRightOf="@+id/tv_source"
-
app:layout_constraintTop_toTopOf="@+id/tv_source"
-
tools:text="07:33" />
-
<View
-
android:layout_width="0dp"
-
android:layout_height="1px"
-
android:layout_margin="10dp"
-
android:background="#EEEEEE"
-
app:layout_constraintTop_toBottomOf="@id/tv_source" />
-
</android.support.constraint.ConstraintLayout>
又改造Adapter
複寫getItemViewType方法,通過判斷圖片的個數區分樣式。
-
@Override
-
public int getItemViewType(int position) {
-
News news = dataItems.get(position);
-
int imgSize = news.imgUrls.size();
-
if (imgSize < 3) {
-
return 0;
-
} else if (imgSize == 3) {
-
return 2;
-
} else {
-
return 3;
-
}
-
}
增加圖集樣式的ViewHolder
-
static class MorePicViewHolder extends RecyclerView.ViewHolder {
-
ImageView ivPic;
-
TextView tvContent;
-
TextView tvCount;
-
TextView tvSource;
-
TextView tvTime;
-
public MorePicViewHolder(View view) {
-
super(view);
-
ivPic = view.findViewById(R.id.iv_pic);
-
tvContent = view.findViewById(R.id.tv_content);
-
tvCount = view.findViewById(R.id.tv_count);
-
tvSource = view.findViewById(R.id.tv_source);
-
tvTime = view.findViewById(R.id.tv_time);
-
}
-
}
在onCreateViewHolder中,通過判斷ViewType的型別,建立對應的ViewHolder。
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
if (viewType == 0) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_one_pic, parent, false);
-
OnePicViewHolder holder = new OnePicViewHolder(view);
-
return holder;
-
} else if (viewType == 2) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_three_pic, parent, false);
-
ThreePicViewHolder holder = new ThreePicViewHolder(view);
-
return holder;
-
} else if (viewType == 3) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_more_pic, parent, false);
-
MorePicViewHolder holder = new MorePicViewHolder(view);
-
return holder;
-
} else {
-
// Can't reach;
-
return null;
-
}
-
}
在onBindingViewHolder中,通過判斷ViewType的型別,繫結對應的資料到控制元件。
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-
int viewType = holder.getItemViewType();
-
if (viewType == 0) {
-
OnePicViewHolder viewHolder = (OnePicViewHolder) holder;
-
News news = dataItems.get(position);
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);
-
} else if (viewType == 2) {
-
ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;
-
News news = dataItems.get(position);
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);
-
} else if (viewType == 3) {
-
MorePicViewHolder viewHolder = (MorePicViewHolder) holder;
-
News news = dataItems.get(position);
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
viewHolder.tvCount.setText(news.count + " 圖");
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);
-
}
-
}
OK,很快又改完了,這次你的心情不太愉快,之前的太不利於擴充套件了,每次新增一種新的都要把Adapter改一遍。
反思
在上次需求上線之後,你開始反思,為什麼我這麼反感產品擴充樣式,而不是我的程式碼有一定的業務相容性,而是把自己搞的這麼被動。算了,下樓喝杯咖啡冷靜冷靜。你順便叫了下旁邊的哥們,他說:“我比較忙,你給我帶杯吧。”,“帶什麼?”你問,“摩卡小杯”。你心想,本來叫你一塊兒去的,你小子這麼懶叫我給你帶。唉,對啊,我的Adapter不就可以這樣嘛,自己不做處理,委託給其他的Adapter去做。
一開始的時候註冊三個的委託Adapter物件,A委託Adapter可以處理一張圖片的樣式,B委託Adapter可以處理三張圖片的樣式,C委託可以處理多張圖片的樣式。根據資料裡面圖片的數量選擇對應的委託Adapter去處理就可以啦。
委託
所有的委託都有相同的方法,為了方便定義一個介面。
-
public interface IDelegateAdapter {
-
// 查詢委託時呼叫的方法,返回自己能處理的型別即可。
-
boolean isForViewType(News news);
-
// 用於委託Adapter的onCreateViewHolder方法
-
RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
-
// 用於委託Adapter的onBindViewHolder方法
-
void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news);
-
}
然後是三個委託
-
public class OnePicDelegateAdapter implements IDelegateAdapter {
-
@Override
-
public boolean isForViewType(News news) {
-
// 我能處理一張圖片
-
return news.imgUrls.size() == 1;
-
}
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_one_pic, parent, false);
-
OnePicViewHolder holder = new OnePicViewHolder(view);
-
return holder;
-
}
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {
-
OnePicViewHolder viewHolder = (OnePicViewHolder) holder;
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);
-
}
-
static class OnePicViewHolder extends RecyclerView.ViewHolder {
-
ImageView ivPic;
-
TextView tvContent;
-
TextView tvSource;
-
TextView tvTime;
-
public OnePicViewHolder(View view) {
-
super(view);
-
ivPic = view.findViewById(R.id.iv_pic);
-
tvContent = view.findViewById(R.id.tv_content);
-
tvSource = view.findViewById(R.id.tv_source);
-
tvTime = view.findViewById(R.id.tv_time);
-
}
-
}
-
}
-
public class ThreePicDelegateAdapter implements IDelegateAdapter {
-
@Override
-
public boolean isForViewType(News news) {
-
// 我能處理三張圖片
-
return news.imgUrls.size() == 3;
-
}
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_three_pic, parent, false);
-
ThreePicViewHolder holder = new ThreePicViewHolder(view);
-
return holder;
-
}
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {
-
ThreePicViewHolder viewHolder = (ThreePicViewHolder) holder;
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic1);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(1)).into(viewHolder.ivPic2);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(2)).into(viewHolder.ivPic3);
-
}
-
static class ThreePicViewHolder extends RecyclerView.ViewHolder {
-
ImageView ivPic1;
-
ImageView ivPic2;
-
ImageView ivPic3;
-
TextView tvContent;
-
TextView tvSource;
-
TextView tvTime;
-
public ThreePicViewHolder(View view) {
-
super(view);
-
ivPic1 = view.findViewById(R.id.iv_pic1);
-
ivPic2 = view.findViewById(R.id.iv_pic2);
-
ivPic3 = view.findViewById(R.id.iv_pic3);
-
tvContent = view.findViewById(R.id.tv_content);
-
tvSource = view.findViewById(R.id.tv_source);
-
tvTime = view.findViewById(R.id.tv_time);
-
}
-
}
-
}
-
public class MorePicDelegateAdapter implements IDelegateAdapter {
-
@Override
-
public boolean isForViewType(News news) {
-
// 我能處理多張圖片
-
return news.imgUrls.size() > 3;
-
}
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_more_pic, parent, false);
-
MorePicViewHolder holder = new MorePicViewHolder(view);
-
return holder;
-
}
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {
-
MorePicViewHolder viewHolder = (MorePicViewHolder) holder;
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
viewHolder.tvCount.setText(news.count + " 圖");
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);
-
}
-
static class MorePicViewHolder extends RecyclerView.ViewHolder {
-
ImageView ivPic;
-
TextView tvContent;
-
TextView tvCount;
-
TextView tvSource;
-
TextView tvTime;
-
public MorePicViewHolder(View view) {
-
super(view);
-
ivPic = view.findViewById(R.id.iv_pic);
-
tvContent = view.findViewById(R.id.tv_content);
-
tvCount = view.findViewById(R.id.tv_count);
-
tvSource = view.findViewById(R.id.tv_source);
-
tvTime = view.findViewById(R.id.tv_time);
-
}
-
}
-
}
再看下之前冗餘的Adapter,現在已經非常清瘦了
-
public class NewsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
-
private List<News> dataItems = new ArrayList<>();
-
public void setDataItems(List<News> dataItems) {
-
this.dataItems = dataItems;
-
notifyDataSetChanged();
-
}
-
List<IDelegateAdapter> delegateAdapters = new ArrayList<>();
-
public void addDelegate(IDelegateAdapter delegateAdapter) {
-
delegateAdapters.add(delegateAdapter);
-
}
-
@Override
-
public int getItemViewType(int position) {
-
}
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
}
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-
}
-
@Override
-
public int getItemCount() {
-
return dataItems.size();
-
}
-
}
有三個方法需要我們去填空,在之前的圖中我們說過,getItemViewType通過委託在集合中的index去標識。
-
@Override
-
public int getItemViewType(int position) {
-
// 找到當前位置的資料
-
News news = dataItems.get(position);
-
// 遍歷所有的代理,問下他們誰能處理
-
for (IDelegateAdapter delegateAdapter : delegateAdapters) {
-
if (delegateAdapter.isForViewType(news)) {
-
// 誰能處理返回他的index
-
return delegateAdapters.indexOf(delegateAdapter);
-
}
-
}
-
throw new RuntimeException("沒有找到可以處理的委託Adapter");
-
}
然後是onCreateViewHolder,既然是通過委託Adapter的在集合中的index去標記的ViewType,那麼在onCreateViewHolder中就非常簡單了。
-
@Override
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
// 找到對應的委託Adapter
-
IDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);
-
// 把onCreateViewHolder交給委託Adapter去處理
-
RecyclerView.ViewHolder viewHolder = delegateAdapter.onCreateViewHolder(parent, viewType);
-
return viewHolder;
-
}
接下來是onBindViewHolder,類似onCreateViewHolder,這裡也是找到委託Adapter,交給他去處理。
-
@Override
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
-
// 找到當前ViewHolder的ViewType,也就是委託Adapter在集合中的index
-
int viewType = holder.getItemViewType();
-
// 找到對應的委託Adapter
-
IDelegateAdapter delegateAdapter = delegateAdapters.get(viewType);
-
// 把onBindViewHolder交給委託Adapter去處理
-
delegateAdapter.onBindViewHolder(holder, position, dataItems.get(position));
-
}
使用的時候也非常簡單,在之前的基礎上註冊委託Adapter就可以了。
-
RecyclerView recyclerView = findViewById(R.id.recycler_view);
-
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
-
recyclerView.setLayoutManager(layoutManager);
-
NewsAdapter mAdapter = new NewsAdapter();
-
// 新增委託Adapter
-
mAdapter.addDelegate(new OnePicDelegateAdapter());
-
mAdapter.addDelegate(new ThreePicDelegateAdapter());
-
mAdapter.addDelegate(new MorePicDelegateAdapter());
-
recyclerView.setAdapter(mAdapter);
雙擴充樣式
產品經理又來了,說又要新增一種視訊型別的樣式。你爽快地說:“沒問題”。
之前一直是通過圖片的個數來判斷是那種形式,但這次明顯不能這麼幹了,修改原來的實體,新增一個type來標識是那種型別。
-
public class News {
-
public int type = 0; // 0:一張圖片;1:三張圖片;2:多張圖片;3:視訊型別
-
public List<String> imgUrls = null;
-
public String content = "";
-
public String count = "";
-
public String duration = "";
-
public String source = "";
-
public String time = "";
-
public String link = "";
-
}
新增xml佈局
-
<?xml version="1.0" encoding="utf-8"?>
-
<android.support.constraint.ConstraintLayout 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="wrap_content"
-
android:paddingLeft="10dp"
-
android:paddingRight="10dp"
-
android:paddingTop="10dp">
-
<TextView
-
android:id="@+id/tv_content"
-
android:layout_width="0dp"
-
android:layout_height="wrap_content"
-
android:ellipsize="end"
-
android:maxLines="2"
-
android:textColor="#333333"
-
android:textSize="18sp"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toTopOf="parent"
-
tools:text="這是一條新聞,這是一條新聞,這是一條新聞" />
-
<ImageView
-
android:id="@+id/iv_pic"
-
android:layout_width="0dp"
-
android:layout_height="0dp"
-
android:layout_marginTop="10dp"
-
android:scaleType="centerCrop"
-
app:layout_constraintDimensionRatio="5:3"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintRight_toRightOf="parent"
-
app:layout_constraintTop_toBottomOf="@+id/tv_content"
-
tools:src="@mipmap/ic_launcher" />
-
<ImageView
-
android:layout_width="48dp"
-
android:layout_height="48dp"
-
android:src="@mipmap/play90"
-
app:layout_constraintBottom_toBottomOf="@+id/iv_pic"
-
app:layout_constraintLeft_toLeftOf="@+id/iv_pic"
-
app:layout_constraintRight_toRightOf="@+id/iv_pic"
-
app:layout_constraintTop_toTopOf="@+id/iv_pic" />
-
<TextView
-
android:id="@+id/tv_duration"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:paddingBottom="6dp"
-
android:paddingRight="10dp"
-
android:textColor="@android:color/white"
-
app:layout_constraintBottom_toBottomOf="@+id/iv_pic"
-
app:layout_constraintRight_toRightOf="@+id/iv_pic"
-
tools:text="12:34" />
-
<TextView
-
android:id="@+id/tv_source"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginTop="10dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintLeft_toLeftOf="parent"
-
app:layout_constraintTop_toBottomOf="@+id/iv_pic"
-
tools:text="澎湃新聞" />
-
<TextView
-
android:id="@+id/tv_time"
-
android:layout_width="wrap_content"
-
android:layout_height="wrap_content"
-
android:layout_marginLeft="8dp"
-
android:textColor="#888888"
-
android:textSize="12sp"
-
app:layout_constraintLeft_toRightOf="@+id/tv_source"
-
app:layout_constraintTop_toTopOf="@+id/tv_source"
-
tools:text="07:33" />
-
<View
-
android:layout_width="0dp"
-
android:layout_height="1px"
-
android:layout_margin="10dp"
-
android:background="#EEEEEE"
-
app:layout_constraintTop_toBottomOf="@id/tv_source" />
-
</android.support.constraint.ConstraintLayout>
新增委託Adapter
-
public class VideoDelegateAdapter implements IDelegateAdapter {
-
@Override
-
public boolean isForViewType(News news) {
-
// 我能處理視訊型別
-
return news.type == 3;
-
}
-
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
-
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_news_video, parent, false);
-
VideoViewHolder holder = new VideoViewHolder(view);
-
return holder;
-
}
-
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, News news) {
-
VideoViewHolder viewHolder = (VideoViewHolder) holder;
-
viewHolder.tvContent.setText(news.content);
-
viewHolder.tvSource.setText(news.source);
-
viewHolder.tvTime.setText(news.time);
-
viewHolder.tvDuration.setText(news.duration);
-
Glide.with(holder.itemView.getContext()).load(news.imgUrls.get(0)).into(viewHolder.ivPic);
-
}
-
static class VideoViewHolder extends RecyclerView.ViewHolder {
-
ImageView ivPic;
-
TextView tvContent;
-
TextView tvDuration;
-
TextView tvSource;
-
TextView tvTime;
-
public VideoViewHolder(View view) {
-
super(view);
-
ivPic = view.findViewById(R.id.iv_pic);
-
tvContent = view.findViewById(R.id.tv_content);
-
tvDuration = view.findViewById(R.id.tv_duration);
-
tvSource = view.findViewById(R.id.tv_source);
-
tvTime = view.findViewById(R.id.tv_time);
-
}
-
}
-
}
添加註冊委託代理
-
// 新增委託Adapter
-
mAdapter.addDelegate(new OnePicDelegateAdapter());
-
mAdapter.addDelegate(new ThreePicDelegateAdapter());
-
mAdapter.addDelegate(new MorePicDelegateAdapter());
-
mAdapter.addDelegate(new VideoDelegateAdapter()); // 新新增的視訊型別
經過非常簡單的三步之後,就很清爽地完成了這次需求,大家都很開心。
叒擴充樣式
產品經理再一次找到你,說我們的新聞很火爆,希望加入廣告。每10條加入一個廣告,是不是很好做。你心想,臥槽,這他麼不是坑我麼。沒辦法,產品經理的性子你也知道,這個肯定是要做的。
這麼看來之前的封裝還是不能滿足的,之前的是要求所有的資料都有相同的型別。那能不能加入不同資料型別的支援呢?
看下之前的是通過一個List來儲存託管Adapter的,這樣是無法儲存不同的型別資訊的。
List<IDelegateAdapter> delegateAdapters = new ArrayList<>();
那就再增加一個儲存型別資訊的List吧,為了便於查詢,這裡使用Android提供的SparseArrayCompat。兩個集合定義如下:
-
// 用於儲存委託Adapter
-
private SparseArrayCompat<AdapterDelegate<Object>> delegates = new SparseArrayCompat();
-
// 用於儲存委託Adapter能處理的型別
-
private SparseArray<String> dataTypes = new SparseArray<>();
-
public AdapterDelegatesManager addDelegate(AdapterDelegate<Object, VH> delegate) {
-
Type superclass = delegate.getClass().getGenericSuperclass();
-
if (superclass instanceof ParameterizedType) {
-
Class<?> clazz = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];
-
String typeWithTag = clazz.getName();
-
int viewType = delegates.size();
-
// Save the delegate to the collection;
-
delegates.put(viewType, delegate);
-
// Save the index of the delegate to the collection;
-
dataTypes.put(viewType, typeWithTag);
-
} else {
-
// Has no generics.
-
throw new IllegalArgumentException(
-
String.format("Please set the correct generic parameters on %s.", delegate.getClass().getName()));
-
}
-
return this;
-
}
每向delegates中新增一個委託Adapter,則向dataTypes中新增該委託Adapter能處理的型別。比如:向delegates新增一個能處理String型別的委託 AdapterDelegate<String>位置是1,那麼向dataType中新增一個java.lang.String位置也是1。那麼在處理String型別的資料的時候在dataType中查詢到對應的位置為1,就可以去delegate的1位置取對應的委託Adapter就可以啦。
叕來需求
經過以上封裝,你開心地工作了相當長的一段時間。有一天產品又找到你,我們要做一個賬單詳情頁,很簡單,就是顯示他的消費資訊。
你一看,上面是固定的資訊,下面是固定的資訊,中間是一個列表。使用ListView的新增頭部、尾部會非常簡單,但是你又不想使用ListView。能不能使用封裝的RecyclerView委託Adapter實現呢?
-
public class Bill {
-
public String title = ""; // 標題
-
public String waiter = ""; // 服務員
-
public String cashier = ""; // 收銀員
-
public int ramadhin = 0; // 桌號
-
public int guests = 0; // 客人數
-
public String beginTime = ""; // 開臺時間
-
public String endTime = ""; // 結賬時間
-
public String duration = ""; // 用餐時長
-
public List<Item> details = null; // 用餐詳情
-
public String total = ""; // 合計
-
public String discounts = ""; // 優惠
-
public String receivable = ""; // 應收
-
public String describe = ""; // 描述資訊
-
public static class Item {
-
public String name = ""; // 名稱
-