編輯:關於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 這種類。
今天群友希望寫一個關於插件的Blog,思來想去,插件也不是很懂,只是用大致的思路看看能不能模擬一個,思路還是比較重要的,如果你有興趣的話,也可以加群:555974449,
在平常的開發中,我們經常會遇到點擊,滑動之類的事件。有時候不同的view之間也存在各種滑動沖突。比如布局的內外兩層都能滑動的話,那麼就會出現沖突了。這個時候我們就需要了解
首選項這個名詞對於熟悉Android的朋友們一定不會感到陌生,它經常用來設置軟件的運行參數。Android提供了一種健壯並且靈活的框架來處理首選項。它提供了簡單的API來
什麼是OpenGL ES?OpenGL(全寫Open Graphics Library)是指定義了一個跨編程語言、跨平台的編程接口規格的專業的圖形程序接口。它用於三維圖像