版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載
引言
在實際項目的開發(fā)中,首頁的布局基本上都是復(fù)雜的 UI,而我們的實現(xiàn)思路一般就是利用 RecyclerView
結(jié)合 getItemType()
,并在適配器里根據(jù)不同的 item
類型去創(chuàng)建不同的 ViewHolder
,最后在 onBindViewHolder()
中依然是根據(jù) item
類型來綁定對應(yīng)的數(shù)據(jù)。這種方法是最基本的方法,相信大家都懂。但是,其缺點也很明顯,就是可擴展性太差。
接下來,我將介紹另一種更為巧妙的方法來實現(xiàn),以期大大提高其擴展性。我們將以 item
的布局 id
作為區(qū)別item
的唯一標志并結(jié)合兩種設(shè)計模式,為大家呈現(xiàn)一種新穎,簡潔的方式來實現(xiàn)此類復(fù)雜布局。其中如有紕漏,望請悉心指出。
必備知識
本實現(xiàn)方法主要用到了 Java
設(shè)計模式中的訪問者模式和工廠方法模式,亦涉及到 ViewHolder
的封裝技巧。
訪問者模式
概念
Java
中的訪問者模式屬于一種行為型設(shè)計模式,核心主要由訪問者與被訪問者兩部分組成。一般對于同一場景來說,被訪問者都是由不同類的類型所表示,而不同的訪問者可以對被訪問者進行不同的訪問操作。其中,被訪問者常利用集合結(jié)構(gòu)來存儲(比如List
),訪問者通過遍歷集合實現(xiàn)對其中存儲的元素的逐個操作。UML類圖

出處:維基百科
UML解讀
該UML
類圖中有兩個類:訪問者(Visitor
)和被訪問者(Element
),然后有多個具體訪問者繼承訪問者 Visitor
(eg: ConcreateVisitor1
),也有多個具體被訪問者繼承被訪問者 Element
(eg: ConcreateElementA
) 。首先,Visitor
中為每個具體被訪問者定義了一個可訪問具體被訪問者操作的方法(通過注入具體被訪問者的引用);其次,Element
中定義了一個接受訪問的方法 accept
,并且依賴注入訪問者 visitor
,以便訪問者可以訪問被訪問者;然后,Object Structure
對象結(jié)構(gòu)主要用于存儲被訪問者。因此,對于每個被訪問者都應(yīng)先從該對象結(jié)構(gòu)中取出來。最后,客戶端 Client
定義集合對象收集被訪問者數(shù)據(jù),通過對集合的遍歷完成訪問者對每一個被訪問元素的訪問操作。
具體例子詳見推薦博客 Java設(shè)計模式之 訪問者模式【Visitor Pattern】
工廠方法模式
-
概念
工廠方法模式是一種實現(xiàn)了“工廠”概念的面向?qū)ο笤O(shè)計模式 ,是處理在不指定對象具體類型的情況下創(chuàng)建對象的問題。工廠方法模式的實質(zhì)是“定義一個創(chuàng)建對象的接口,但讓實現(xiàn)這個接口的類來決定實例化哪個類。工廠方法讓類的實例化推遲到子類中進行。”
-
UML類圖
工廠模式
出處:維基百科
UML解讀
首先在創(chuàng)建器 Creator
中定義一個工廠方法用于生產(chǎn)未指定具體類型的產(chǎn)品,其次,子類--具體創(chuàng)建器 ConcreteCreator
實現(xiàn)父類工廠方法,給出創(chuàng)建具體產(chǎn)品類型的實現(xiàn),最后,客戶端只需調(diào)用具體創(chuàng)建者中的方法即可得到所需的產(chǎn)品。
具體例子詳見推薦博客工廠方法模式
BaseViewHolder的封裝
/**
* 封裝的 viewholder 用于獲取各個 item 上的控件 采用集合存儲取過的控件
*/
public class BaseViewHolder extends RecyclerView.ViewHolder {
protected View itemView;//每個item的布局視圖view
protected SparseArray<View> list;//itemView上所有控件的集合
public BaseViewHolder(View itemView) {
super(itemView);
this.itemView = itemView;
list=new SparseArray<>();
}
/**
*獲取 itemView 上控件
*/
public <T> T getView(int id) {
View view = list.get(id);
if (view == null) {
view = itemView.findViewById(id);
list.put(id, view);
}
return (T) view;
}
BaseViewHolder
繼承至 RecyclerView.ViewHolder
,可作為 Recyclerview
適配器中通用的 ViewHolder
來使用,因此你無需在每個適配器里面再定義內(nèi)部類 MyViewHolder
。BaseViewHolder
可以在構(gòu)造器中獲取到每個 item
的 itemView
,然后就可以定義一個快速獲取 itemView
上各個子控件的方法 getView
,以后在適配器中獲取 item
子控件能夠隨時調(diào)用此方法.
BetterViewHolder
public abstract class BetterViewHolder<T> extends BaseViewHolder {
public BetterViewHolder(View itemView) {
super(itemView);
}
/**
* 綁定 item 的數(shù)據(jù)
* @param t 每個item的實體引用
*/
public abstract void bindDataToItem(T t,int position);
}
BetterViewHolder
聲明為抽象類型,在 BaseViewHolder
基礎(chǔ)上再次封裝了一層。主要定義了一個抽象方法用于實現(xiàn)適配器中 onBindViewHolder(T t,int position)
的功能。但由于子類的實體類型不可確定,故需要借助泛型技巧,定義T來表示子類的泛型。因此,子類只需繼承該類,并指定子類所需的實體類型即可。
實例解析
-
本例中定義了四種
item
布局類型,如下圖所示
布局代碼見底部源碼
- 定義訪問者
TypeFactory
public abstract class TypeFactory {
public abstract int type(Banner banner);
public abstract int type(Category category);
public abstract int type(Item item);
public abstract int type(Footer footer);
//工廠方法模式應(yīng)用
public abstract BetterViewHolder onCreateViewHolder(View itemView,int type);
}
- 定義被訪問者
Visitable
/*
*定義抽象的被訪問者 type方法,用來接收/引用一個抽象訪問者對象,以便利用這個對象進行操作;
*/
public abstract class Visitable {
public abstract int type(TypeFactory factory);
}
-
定義四個實體類并繼承
Visitable
/** * Created by hrx on 2017/4/30. * 具體的被訪問者 * 實現(xiàn)type抽象方法,通過傳入的具體訪問者參數(shù)、 * 調(diào)用具體訪問者對該對象的訪問操作方法實現(xiàn)訪問邏輯; * 比如這就是利用抽象訪問者 TypeFactory 引用來調(diào)用操作方法獲取對應(yīng)該 Banner 關(guān)聯(lián)的布局 id */ public class Banner extends Visitable{ @Override public int type(TypeFactory factory) { return factory.type(this); } } public class Category extends Visitable { @Override public int type(TypeFactory factory) { return factory.type(this); } } public class Item extends Visitable { private int position; @Override public int type(TypeFactory factory) { return factory.type(this); } public int getPosition() { return position; } public void setPosition(int position) { this.position = position; } } public class Footer extends Visitable { @Override public int type(TypeFactory factory) { return factory.type(this); } }
-
具體訪問者
TypeFactoryList
/* * 具體的訪問者實現(xiàn)了抽象訪問者的方法 * 同時 onCreateViewHolder 也是利用工廠方法模式創(chuàng)建了各個 item 的 viewholder 實例 */ public class TypeFactoryList extends TypeFactory { //聲明每個item的布局id public static final int BANNER = R.layout.banner; public static final int CATEGORY = R.layout.category; public static final int ITEM = R.layout.item; public static final int FOOTER = R.layout.footer; @Override public int type(Banner banner) { return BANNER; } @Override public int type(Category category) { return CATEGORY; } @Override public int type(Item item) { return ITEM; } @Override public int type(Footer footer) { return FOOTER; } @Override public BetterViewHolder onCreateViewHolder(View itemView, int type) { BetterViewHolder viewHolder = null; switch (type) { case BANNER: viewHolder = new BannerViewHolder(itemView); break; case CATEGORY: viewHolder = new CategoryViewHolder(itemView); break; case ITEM: viewHolder = new ItemViewHolder(itemView); break; case FOOTER: viewHolder = new FooterViewHolder(itemView); break; default: break; } return viewHolder; } }
-
定義適配器
public class MainActivityAdapter extends RecyclerView.Adapter<BetterViewHolder> { private List<Visitable> mVisitables; private TypeFactory factory; public MainActivityAdapter(List<Visitable> mVisitables) { this.mVisitables = mVisitables; factory = new TypeFactoryList(); } //此 ViewHolder 的創(chuàng)建細節(jié)已經(jīng)抽象到 TypeFactoryList 中去實現(xiàn)了 此處等同與獲取工廠生產(chǎn)的產(chǎn)品 @Override public BetterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = View.inflate(parent.getContext(), viewType, null); return factory.onCreateViewHolder(itemView, viewType); } //此處的實現(xiàn)交由 BetterViewHolder 的各個子類去實現(xiàn),故此處 Java 會根據(jù)相應(yīng)的子類去獲取其下實現(xiàn)的 bindDataToItem(),利用JAVA動態(tài)分派而無需進行類型檢查 @Override public void onBindViewHolder(BetterViewHolder holder, int position) { holder.bindDataToItem(mVisitables.get(position),position); } //此處即代表訪問者模式中的客戶端調(diào)用被訪問者的 type(),進行訪問操作獲取其布局 id @Override public int getItemViewType(int position) { return mVisitables.get(position).type(factory); } @Override public int getItemCount() { return mVisitables.size(); } }
該適配器的數(shù)據(jù)就是得到訪問者模式中的對象結(jié)構(gòu) Object Structure
中存儲的被訪問者集合,對應(yīng)到例子中就是 mVisitables
集合。同時需要一個訪問者引用,以便該適配器(客戶端)能夠利用這個引用獲取每個被訪問者關(guān)聯(lián)到的布局Id(利用該引用執(zhí)行某些操作),如mVisitables.ge(position).type(factory)
可以獲取到每個被訪問者關(guān)聯(lián)到的布局 id
。
-
四個
BetterViewHolder
的子類四個子類代表了其對應(yīng)
item
的ViewHolder
,可以在該類上實現(xiàn)item
上的操作,比如點擊事件,設(shè)置item
上子控件的所有數(shù)據(jù)等。
代碼見底部源碼
-
Activity
收集數(shù)據(jù)并設(shè)置適配器
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Visitable> mVisitable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
//默認4列
final GridLayoutManager manager = new GridLayoutManager(this, 4);
//此方法定義每個item占幾列,有點類似線性布局的權(quán)重屬性
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position > 2 && position < 7) {
return 1;
}
return 4;
}
});
recyclerView.setLayoutManager(manager);
initData();
recyclerView.setAdapter(new MainActivityAdapter(mVisitable));
}
private void initData() {
mVisitable = new ArrayList<>();
//按布局的順序依次加入各個被訪問者
Banner banner = new Banner();
mVisitable.add(banner);
Category category = new Category();
mVisitable.add(category);
//加入4個item
for (int i = 0; i < 5; i++) {
Item item = new Item();
item.setPosition(i + 2);
mVisitable.add(item);
}
Footer footer = new Footer();
mVisitable.add(footer);
}
}
注:
mVisitable
集合中被訪問者的加入順序即代表了最終顯示出來的順序,并且集合中的每個元素僅能代表其中一個item
,意味著如果你要重復(fù)該item
就要重復(fù)聲明一個實體再加入集合中。
總結(jié)
利用訪問者模式和工廠方法模式大大解耦了上述復(fù)雜布局的實現(xiàn)過程,同時可擴展性大大提高。如果往后還需修改布局,只需修改對應(yīng) item
的布局文件和數(shù)據(jù)的綁定。而若是增加 item
,那么只需定義新的實體加入被訪問者集合中,同時編寫布局文件及對應(yīng)的 ViewHolder
實現(xiàn)即可。這樣一來,不同 item
間就不會相互影響,變得易維護和易擴展,相信你學(xué)會之后,一定會愛上此法。
最后,謝謝你看到這里,歡迎交流意見。
感謝
[譯]關(guān)于 Android Adapter,你的實現(xiàn)方式可能一直都有問題
Java設(shè)計模式之 訪問者模式【Visitor Pattern】
Java設(shè)計模式之 工廠方法模式