Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發-優雅的實現多類型列表的Adapter

Android開發-優雅的實現多類型列表的Adapter

編輯:關於Android編程

引言

在開發中經常會遇到,一個列表(RecyclerView)中有多種布局類型的情況。

文中主要從設計的角度闡釋如何更合理的實現多種布局類型的Adapter,本文主要從實踐的角度出發,站在巨人的肩膀上,結合我個人的理解進行闡述,如果有纰漏,歡迎留言指出。

有多種布局類型

有時候,由於應用場景的需要,列表(RecyclerView)中需要存在一種以上的布局類型。為了闡述的方便,我們先假設一種應用場景

列表中含有若干個常規的布局,在列表的中的第一個位置與第二個位置中分別為兩個不同的布局,其余為常規的布局

針對這樣的需求,筆者一直以來的實現方式如下

privatefinalintITEM_TYPE_ONE=1;

privatefinalintITEM_TYPE_TWO=2;

@Override

publicintgetItemViewType(intposition){

if(0==position){

returnITEM_TYPE_ONE;

}elseif(1==position){

returnITEM_TYPE_TWO;

}

returnsuper.getItemViewType(position);

}

@Override

publicViewHolderonCreateViewHolder(ViewGroupparent,intviewType){

if(ITEM_TYPE_ONE==viewType){

returnnewOneViewHolder();

}elseif(ITEM_TYPE_TWO==viewType){

returnnewTwoViewHolder();

}

returnnewNormalViewHolder();

}

@Override

//偽代碼

publicvoidonBindViewHolder(ViewHolderholder,intposition){

if(holderinstanceofOneViewHolder){

...

}elseif(holderinstanceofTwoViewHolder){

...

}else{

...

}

}

在Adapter的getItemViewType方法中返回特定位置的特定標識(根據前文需求,就是position0與position1)

在onCreateViewHolder中根據viewType參數,也就是getItemViewType的返回值來判斷需要創建的ViewHolder類型

在onBindViewHolder方法中對ViewHolder的具體類型進行判斷,分別為不同類型的ViewHolder進行綁定數據與邏輯處理

通過以上就能實現多類型列表的Adapter,但這樣的代碼寫多了總會覺得別扭,特別是看到了[譯]關於 Android Adapter,你的實現方式可能一直都有問題這篇文章之後。

結合文章與我個人的理解,這種實現方式所存在弊端可以總結為以下幾點:

類型檢查與類型轉型,由於在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 ”,就給自己一耳光。

不利於擴展,目前的需求是列表中存在三種布局類類型,那麼如果需求變動,極端一點的情況就是數據源是從服務器獲取的,數據中的model決定列表中的布局類型。這種情況下,每當model改變或model類型增加,我們都要去改變adapter中很多的代碼,同時Adapter還必須知道特定的model在列表中的位置(position)除非跟服務端約定好,model(位置)不變,很顯然,這是不現實的。

[譯]關於 Android Adapter,你的實現方式可能一直都有問題中是這樣說的

另外,我們實行那些 adapter 的方法違背了SOLID原則中的“開閉准則” 。它是這樣說的:“對擴展開放,對修改封閉。” 當我們添加另一個類型或者 model 到我們的類中時,比如叫 Rabbit 和 RabbitViewHolder,我們不得不在 Adapter 裡改變許多的方法。 這是對開閉原則明顯的違背。添加新對象不應該修改已存在的方法。

不利於維護,這點應該是上一點的延伸,隨著列表中布局類型的增加與變更,getItemViewType、onCreateViewHolder、onBindViewHolder中的代碼都需要變更或增加,Adapter 中的代碼會變得臃腫與混亂,增加了代碼的維護成本。

首先讓我摸摸自己的臉,然後結合[譯]關於 Android Adapter,你的實現方式可能一直都有問題,看看如何優雅的實現多類型列表的Adapter

優雅的實現

結合上文,我們的核心目的就是三個

避免類的類型檢查與類型轉型

增強Adapter的擴展性

增強Adapter的可維護性

前文提到了,當列表中類型增加或減少時Adapter中主要改動的就是getItemViewType、onCreateViewHolder、onBindViewHolder這三個方法,因此,我們就從這三個方法中開始著手。

Talk is cheap. Show me the code,圍繞以上幾點,開始碼代碼

getItemViewType

原本的代碼是這樣

@Override

publicintgetItemViewType(intposition){

if(0==position){

returnITEM_TYPE_ONE;

}elseif(1==position){

returnITEM_TYPE_TWO;

}

returnsuper.getItemViewType(position);

}

在這段代碼中,我們必須知道特定的布局類型在列表中的位置,而布局類型在列表中的位置是由數據源決定的,為了解決這個問題並且減少if之類的邏輯判斷簡化代碼,我們可以簡單粗暴的在Model中增加type標識,優化之後getItemViewType的實現大致如下

@Override

publicintgetItemViewType(intposition){

returnmodelList.get(position).getType();

}

這樣的方式有很大的局限性(誰用誰知道),這裡就不展開了,直接看正確的姿勢,先看代碼(具體可以看源碼)

publicinterfaceVisitable{

inttype(TypeFactorytypeFactory);

}

publicclassOneimplementsVisitable{

...

...

@Override

publicinttype(TypeFactorytypeFactory){

returntypeFactory.type(this);

}

}

publicclassTwoimplementsVisitable{

...

...

@Override

publicinttype(TypeFactorytypeFactory){

returntypeFactory.type(this);

}

}

publicclassNormalimplementsVisitable{

...

...

@Override

publicinttype(TypeFactorytypeFactory){

returntypeFactory.type(this);

}

}

publicinterfaceTypeFactory{

inttype(Oneone);

inttype(Twotwo);

}

publicclassTypeFactoryForListimplementsTypeFactory{

privatefinalintTYPE_RESOURCE_ONE=R.layout.layout_item_one;

privatefinalintTYPE_RESOURCE_TWO=R.layout.layout_item_two;

privatefinalintTYPE_RESOURCE_NORMAL=R.layout.layout_item_normal;

@Override

publicinttype(Oneone){

returnTYPE_RESOURCE_ONE;

}

@Override

publicinttype(Twoone){

returnTYPE_RESOURCE_TWO;

}

@Override

publicinttype(Normalnormal){

returnTYPE_RESOURCE_NORMAL;

}

...

}

針對getItemViewType可以進行如下實現

privateListmodelList;

@Override

publicintgetItemViewType(intposition){

returnmodelList.get(position).type(typeFactory);

}

小結:

通過接口抽象,將所有與列表相關的Model抽象為Visitable,當我們在初始化數據源時就能以List的形式將不同類型的Model集合在列表中;

通過訪問者模式,將列表類型判斷的相關代碼抽取到TypeFactoryForList 中,同時所有列表類型對應的布局資源都在這個類中進行管理與維護,以這樣的方式巧妙的增強了擴展性與可維護性;

getItemViewType中不再需要進行if判斷,通過數據源控制列表的布局類型,同時返回的不再是簡單的布局類型標識,而是布局的資源ID(通過modelList.get(position).type()獲取),進一步簡化代碼(在onCreateViewHolder中會體現出來);

onCreateViewHolder

結合上文可以了解到,getItemViewType返回的是布局資源ID,也就是onCreateViewHolder(ViewGroup parent, int viewType)參數中的viewType,我們可以直接用viewType創建itemView,但是,問題來了,itemView創建之後,還是需要進行類型判斷,創建不同的ViewHolder,針對這個問題可以分以下幾個步驟解決

首先為了增強ViewHolder的靈活性,可以繼承RecyclerView.ViewHolder派生出BaseViewHolder抽象類如下

publicabstractclassBaseViewHolderextendsRecyclerView.ViewHolder{

privateSparseArrayviews;

privateViewmItemView;

publicBaseViewHolder(ViewitemView){

super(itemView);

views=newSparseArray<>();

this.mItemView=itemView;

}

publicViewgetView(intresID){

Viewview=views.get(resID);

if(view==null){

view=mItemView.findViewById(resID);

views.put(resID,view);

}

returnview;

}

publicabstractvoidsetUpView(Tmodel,intposition,MultiTypeAdapteradapter);

}

不同的ViewHolder繼承BaseViewHolder並實現setUpView方法即可。

然後對TypeFactory 與TypeFactoryForList 增加如下代碼

publicinterfaceTypeFactory{

...

BaseViewHoldercreateViewHolder(inttype,ViewitemView);

}

publicclassTypeFactoryForListimplementsTypeFactory{

privatefinalintTYPE_RESOURCE_ONE=R.layout.layout_item_one;

privatefinalintTYPE_RESOURCE_TWO=R.layout.layout_item_two;

privatefinalintTYPE_RESOURCE_NORMAL=R.layout.layout_item_normal;

...

@Override

publicBaseViewHoldercreateViewHolder(inttype,ViewitemView){

if(TYPE_RESOURCE_ONE==type){

returnnewOneViewHolder(itemView);

}elseif(TYPE_RESOURCE_TWO==type){

returnnewTwoViewHolder(itemView);

}elseif(TYPE_RESOURCE_NORMAL==type){

returnnewNormalViewHolder(itemView);

}

returnnull;

}

}

最後對onCreateViewHolder方法進行如下實現

@Override

publicBaseViewHolderonCreateViewHolder(ViewGroupparent,intviewType){

Contextcontext=parent.getContext();

ViewitemView=View.inflate(context,viewType,null);

returntypeFactory.createViewHolder(viewType,itemView);

}

小結:

在onCreateViewHolder中以BaseViewHolder作為返回值類型。因為BaseViewHolder作為不同類型的ViewHolder的基類,可以避免在onBindViewHolder中對ViewHolder進行類型檢查與類型轉換,同時也可以簡化onBindViewHolder方法中的代碼(具體會在下文闡述);

創建不同類型的ViewHolder的相關代碼被抽取到了TypeFactoryForList 中,簡化了onCreateViewHolder中的代碼,同時與類型相關的代碼都集中在TypeFactoryForList 中,方便後期維護與拓展;

onBindViewHolder

經過以上實現,onBindViewHolder中的代碼就非常的輕盈了,如下

@Override

publicvoidonBindViewHolder(BaseViewHolderholder,intposition){

holder.setUpView(models.get(position),position,this);

}

可以看到,在onBindViewHolder中不需要對ViewHolder進行類型檢查與轉換,也不需要針對不同類型的ViewHoler執行不同綁定操作,不同的列表布局類型的數據綁定(邏輯代碼)都交給了與其自身對應的ViewHolder處理,如下(setUpView中的代碼可根據實際情況修改)

publicclassNormalViewHolderextendsBaseViewHolder{

publicNormalViewHolder(ViewitemView){

super(itemView);

}

@Override

publicvoidsetUpView(finalNormalmodel,intposition,MultiTypeAdapteradapter){

finalTextViewtextView=(TextView)getView(R.id.normal_title);

textView.setText(model.getText());

textView.setOnClickListener(newView.OnClickListener(){

@Override

publicvoidonClick(Viewview){

Toast.makeText(textView.getContext(),model.getText(),Toast.LENGTH_SHORT).show();

}

});

}

}

小結

onBindViewHolder中不需要進行類型檢查與轉換,對ItemView的數據綁定與邏輯處理都交由各自的ViewHolder進行處理。通過這樣方式,讓代碼更整潔,更易於維護,同時也增強了擴展性。

總結

經過如上優化之後,Adapter中的代碼如下

publicclassMultiTypeAdapterextendsRecyclerView.Adapter{

privateTypeFactorytypeFactory;

privateListmodels;

publicMultiTypeAdapter(Listmodels){

this.models=models;

this.typeFactory=newTypeFactoryForList();

}

@Override

publicBaseViewHolderonCreateViewHolder(ViewGroupparent,intviewType){

Contextcontext=parent.getContext();

ViewitemView=View.inflate(context,viewType,null);

returntypeFactory.createViewHolder(viewType,itemView);

}

@Override

publicvoidonBindViewHolder(BaseViewHolderholder,intposition){

holder.setUpView(models.get(position),position,this);

}

@Override

publicintgetItemCount(){

if(null==models){

return0;

}

returnmodels.size();

}

@Override

publicintgetItemViewType(intposition){

returnmodels.get(position).type(typeFactory);

}

}

當列表中增加類型時:

為該類型創建實現了Visitable接口的Model類

創建繼承於BaseViewHolder的ViewHolder(與Model類對應)

為TypeFactory增加type方法(與Model類對應) ,同時TypeFactoryForList 實現該方法

為TypeFactoryForList增加與列表類型對應的資源ID參數

修改TypeFactoryForList 中的createViewHolder方法

可以看到,雖然Adapter中的代碼量減少,但總體的代碼量並沒減少(可能還增多了),但是和好處比起來,增加一點代碼量還是值得的

拓展性——Adapter並不關心不同的列表類型在列表中的位置,因此對於Adapter來說列表類型可以隨意增加或減少,我們只需要維護好數據源即可。

可維護性——不同的列表類型由不同的ViewHolder維護,相互之間互不干擾;對類型的管理都在TypeFactoryForList 中,TypeFactoryForList 中的代碼量少,代碼簡潔,維護成本低。

避免了類的類型檢查與類型轉型,這點看源碼就可以知道

最後

可能還有待完善的地方,大家可以根據實際情況進行修改與擴展。同時,歡迎留言交流。

當然,現今有很多技術人員,一直對官方的RecyclerView的使用及擴展延伸,乃至封裝便捷性,孜孜不倦的研究著。

提供個干貨:DataBindingAdapter:https://github.com/markzhai/DataBindingAdapter

使用 Data Binding 技術的超級簡單的 RecyclerView adapter,再也不需要寫什麼adapter了! 你也無須為此額外創建 ViewHolder 或者 ItemView 這種類。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved