今天看見一篇文章,講到了RecyclerView一些關於加載多種佈局樣式時通常用法存在的一些問題,下面是文章地址:http://www.jianshu.com/p/c6a44e18badb 這裏先看一下通常大多數人的用法,如下:
package com.jason.recycleview.lylrecycleviewadpdemo;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
public class MyAdpter extends RecyclerView.Adapter {
private int type1 = 1;
private int type2 = 2;
private Context mContext;
private List<Data> mList;
public MyAdpter(Context con, List<Data> list) {
this.mContext = con;
this.mList = list;
}
@Override
public int getItemViewType(int position) {
if (position == 0)
return type1;
if (position == 1)
return type2;
return super.getItemViewType(position);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == type1) {
return new ViewHolder1();
} else if (viewType == type2) {
return new ViewHolder2();
}
return new ViewHolder3();
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolder1) {
.....
}else if(holder instanceof ViewHolder2){
.....
}
else if(holder instanceof ViewHolder3){
.....
}
}
@Override
public int getItemCount() {
return mList.size();
}
}
據文章上說的這樣寫存在三種弊端:
1. 類型檢查與類型轉型,由於在onCreateViewHolder根據不同類型創建了不同的ViewHolder,所以在onBindViewHolder需要針對不同類型的ViewHolder進行數據綁定與邏輯處理,這導致需要通過instanceof對ViewHolder進行類型檢查與類型轉型。引用文章上的一句話:
[譯]關於Android Adapter,你的實現方式可能一直都有問題中是這樣說的
許多年前,我在我的顯示器上貼了許多的名言。其中的一個來自 Scott Meyers
寫的《Effective C++》 這本書(最好的IT書籍之一),它是這麼說的:
不管什麼時候,只要你發現自己寫的代碼類似於 “ if the object is of type T1, then do something, but if it’s of type T2, then do something else ”,就給自己一耳光。
2. 我們的這種寫法已經違背了 SOLID 原則中的“開閉準則”。即“對擴展開放,對修改封閉。當我們有新的佈局需要添加進來時我們需要在原來的Adpter中添加type標記,要在getItemViewType()方法中添加兼容,要在onCreateViewHolder()、onBindViewHolder()中添加新的if、else處理。
3. 增加了日後的維護成本。隨着我們佈局的不斷增加,我們的Adpter會變得越來越龐大。
針對上邊的三個問題,網上已經有大牛進行了相關優化,在此膜拜一下,表示一下感謝,我也自己寫了一下代碼,後邊會附上代碼的下載地址,那麼下邊看一下是怎麼進行的優化吧。
先看一下代碼:
對所有與列表相關的Model 層數據進行抽象封裝(BaseValue),當我們在初始化數據源時就能以List<BaseValue>的形式將不同類型的Model集合在列表中;
將列表類型判斷的相關代碼抽取到ViewTypeFactory中,同時所有列表類型對應的佈局資源都在這個類中進行管理維護,增強了擴展性與可維護性;
這裏用到了設計模式中的訪問者設計模式,不懂的同學可以先去了解一下相關知識,說一下這裏的應用:我們可以在我們的Apdter中實例化一個BaseViewTypeFactory對象,通過獲取數據源中對應的model類型調用自己的getLayoutId()方法傳入我們的BaseViewTypeFactory對象,看代碼可以知道在我們的model實現類的getLayoutId()方法中傳入的ViewTypeFactory又調用的相應的type()方法,傳入了個自對應的model類型,看Factory實現類中根據不同model重載了相應的type()方法,返回的是model對應的佈局的.xml文件在R文件中的id,也就是說在getViewItemType()方法中經過這一系列的調用以後最終返回的爲對應的layoutId;及在MyAdpter的OnCreatViewHolder(ViewGrop parent,int viewType)中第二個參數不再是我們自己定義的了,而是當前想要生成的ViewHolder中綁定View的LayoutId;看下面MyAdpter中代碼:
現在我們可以直接用viewType創建itemView,但是,itemView創建之後,還是需要進行類型判斷,創建不同的ViewHolder,那我們來繼續處理,我們將RecyclerView.ViewHolder進行抽象,每一種佈局對應一個自己的ViewHolder,下面看代碼實現:
public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {
private SparseArray<View> mArray;
private View mView;
public BaseViewHolder(View itemView) {
super(itemView);
this.mView = itemView;
mArray = new SparseArray<>();
}
public View getView(int resId) {
View view = mArray.get(resId);
if (view == null) {
view = mView.findViewById(resId);
mArray.put(resId, view);
}
return view;
}
public abstract void setUpView(T modle,int position,MyAdpter adpter);
}
public class ViewHolderOne extends BaseViewHolder<Value1> {
public ViewHolderOne(View itemView) {
super(itemView);
}
@Override
public void setUpView(final Value1 modle, int position, MyAdpter adpter) {
final TextView tv = (TextView) getView(R.id.tv1);
tv.setText(modle.getName());
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(tv.getContext(), modle.getName(), Toast.LENGTH_SHORT).show();
}
});
}
}
public class ViewHolderTwo extends BaseViewHolder<Value2> {
public ViewHolderTwo(View itemView) {
super(itemView);
}
@Override
public void setUpView(Value2 modle, int position, MyAdpter adpter) {
TextView tv= (TextView) getView(R.id.tv2);
tv.setText(modle.getAddress());
}
}
我們繼承RecyclerView.ViewHolder做擴展,保存item對應生成的View,通過一個getView()方法用SparseArray做我們要操作的子View的緩存,並且拿到相應的View;這個getView方法是供子類調用的;setUpView()抽象方法子類實現操作自己對應的子View。
定義好了我們的每一種item對應的ViewHolder後,再回到我們的BaseViewTypeFactory
基類添加一個抽象的onCreatViewHolder()方法,ViewTypeFactory類中實現,看下邊代碼:
public interface BaseViewTypeFactory {
int type(Value1 type);
int type(Value2 type);
int type(Value3 type);
BaseViewHolder creatViewHolder(View v,int viewType);
}
public class ViewTypeFactory implements BaseViewTypeFactory {
private final int oneId = R.layout.viewtype1;
private final int twoId = R.layout.viewtype2;
private final int threeId = R.layout.viewtype3;
@Override
public int type(Value1 type) {
return oneId;
}
@Override
public int type(Value2 type) {
return twoId;
}
@Override
public int type(Value3 type) {
return threeId;
}
@Override
public BaseViewHolder creatViewHolder(View v, int viewType) {
BaseViewHolder mv = null;
if (viewType == oneId)
mv = new ViewHolderOne(v);
else if (viewType == twoId)
mv = new ViewHolderTwo(v);
else if (viewType == threeId)
mv = new ViewHolderThree(v);
return mv;
}
}
這樣處理好以後我們就可以在我們adpter類中的onCreatViewHolder()方法中通過我們已經實例好的factory類進行ViewHolder的初始化了看代碼:
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = View.inflate(parent.getContext(), viewType, null);
return mFactory.creatViewHolder(v, viewType);
}
分析一下這段代碼,首先通過viewType生成View,然後調用creatViewHolder()方法,在方法里根據不同的viewType生成對應的ViewHolder返回;這樣一來我們將判斷移出了Adpter類,到這裏我們的onCreatViewHolder()方法就優化完了,就兩行代碼,生成View,調用creatViewHolder()生成ViewHolder。是不是感覺Adpter中清新了不少。
然後就是onBindViewHolder()方法,這就簡單了,還是看代碼:
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.setUpView(mList.get(position), position, this);
}
就一行代碼,因爲我們在每一個ViewHolder中實現了自己的setUpView()方法,先拿一個看一下:
@Override
public void setUpView(final Value1 modle, int position, MyAdpter adpter) {
final TextView tv = (TextView) getView(R.id.tv1);
tv.setText(modle.getName());
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(tv.getContext(), modle.getName(), Toast.LENGTH_SHORT).show();
}
});
}
這是ViewHolder1中的setUpView實現,分析一下:我們在Adpter中的數據源中拿到model與對應的position位置信息,調用setUpView(),在ViewHolder1中通過父類中的getView()方法得到要操作的子View,從model中得到值進行操作。
到目前爲止我們的多佈局RecyclerView就優化完了,看一下我們的Adpter是不是很清晰:
@Override
public int getItemViewType(int position) {
return mList.get(position).getLayoutId(mFactory);
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = View.inflate(parent.getContext(), viewType, null);
return mFactory.creatViewHolder(v, viewType);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.setUpView(mList.get(position), position, this);
}
下面我們把我們activity中的代碼與Adpter中代碼也附在下邊,佈局文件沒什麼好說的,就是一個RecyclerView,沒什麼特殊的:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerview = (RecyclerView) findViewById(R.id.recyclerview);
recyclerview.setLayoutManager(new LinearLayoutManager(this));
recyclerview.setAdapter(new MyAdpter(this));
}
}
public class MyAdpter extends RecyclerView.Adapter<BaseViewHolder> {
private Context mContext;
private List<BaseValue> mList;
private BaseViewTypeFactory mFactory;
public MyAdpter(Context con) {
this.mContext = con;
mFactory = new ViewTypeFactory();
mList = new ArrayList<>();
mList.add(new Value1("劉大"));
mList.add(new Value3("8"));
mList.add(new Value1("王二"));
mList.add(new Value1("張三"));
mList.add(new Value2("北京"));
mList.add(new Value3("11"));
mList.add(new Value1("李四"));
mList.add(new Value2("上海"));
mList.add(new Value1("陳五"));
mList.add(new Value3("32"));
mList.add(new Value1("週六"));
mList.add(new Value2("深圳"));
mList.add(new Value1("小明"));
mList.add(new Value1("小光"));
mList.add(new Value1("小紅"));
mList.add(new Value3("48"));
mList.add(new Value2("天津"));
mList.add(new Value2("廣州"));
}
@Override
public int getItemViewType(int position) {
return mList.get(position).getLayoutId(mFactory);
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = View.inflate(parent.getContext(), viewType, null);
return mFactory.creatViewHolder(v, viewType);
}
@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
holder.setUpView(mList.get(position), position, this);
}
@Override
public int getItemCount() {
return mList.size();
}
}
我們在adpter創建的時候載入了不同類型的數據,下邊看一下運行結果:
當我們有新的類型佈局進來後,看看我們需要改哪些地方:
1.新建對應model。
2.做Factory兼容。
3.新建新的ViewHolder,實現setUpView()方法就行了。
是不是很簡單,我們的Adpter不用動了,是不是擴展性比原來好多了,而且沒有了類型的對比轉換,維護性也好了,不用管Adpter了,只需做以上三步,就完成了我們新類型佈局的添加。好的,這篇文章就寫到這裏,如果你感覺說的不是很清楚,可以下載源碼閱讀源碼,希望對你有所幫助!
這裏附上Demo的下載地址:http://download.csdn.net/detail/liuyonglei1314/973648