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

出處:維基百科
UML解讀
該UML
類圖中有兩個(gè)類:訪問(wèn)者(Visitor
)和被訪問(wèn)者(Element
),然后有多個(gè)具體訪問(wèn)者繼承訪問(wèn)者 Visitor
(eg: ConcreateVisitor1
)摇庙,也有多個(gè)具體被訪問(wèn)者繼承被訪問(wèn)者 Element
(eg: ConcreateElementA
) 。首先遥缕,Visitor
中為每個(gè)具體被訪問(wèn)者定義了一個(gè)可訪問(wèn)具體被訪問(wèn)者操作的方法(通過(guò)注入具體被訪問(wèn)者的引用)卫袒;其次,Element
中定義了一個(gè)接受訪問(wèn)的方法 accept
单匣,并且依賴注入訪問(wèn)者 visitor
夕凝,以便訪問(wèn)者可以訪問(wèn)被訪問(wèn)者;然后户秤,Object Structure
對(duì)象結(jié)構(gòu)主要用于存儲(chǔ)被訪問(wèn)者码秉。因此,對(duì)于每個(gè)被訪問(wèn)者都應(yīng)先從該對(duì)象結(jié)構(gòu)中取出來(lái)鸡号。最后转砖,客戶端 Client
定義集合對(duì)象收集被訪問(wèn)者數(shù)據(jù),通過(guò)對(duì)集合的遍歷完成訪問(wèn)者對(duì)每一個(gè)被訪問(wèn)元素的訪問(wèn)操作鲸伴。
具體例子詳見(jiàn)推薦博客 Java設(shè)計(jì)模式之 訪問(wèn)者模式【Visitor Pattern】
工廠方法模式
-
概念
工廠方法模式是一種實(shí)現(xiàn)了“工廠”概念的面向?qū)ο笤O(shè)計(jì)模式 ,是處理在不指定對(duì)象具體類型的情況下創(chuàng)建對(duì)象的問(wèn)題府蔗。工廠方法模式的實(shí)質(zhì)是“定義一個(gè)創(chuàng)建對(duì)象的接口,但讓實(shí)現(xiàn)這個(gè)接口的類來(lái)決定實(shí)例化哪個(gè)類挑围。工廠方法讓類的實(shí)例化推遲到子類中進(jìn)行礁竞。”
-
UML類圖
工廠模式
出處:維基百科
UML解讀
首先在創(chuàng)建器 Creator
中定義一個(gè)工廠方法用于生產(chǎn)未指定具體類型的產(chǎn)品杉辙,其次模捂,子類--具體創(chuàng)建器 ConcreteCreator
實(shí)現(xiàn)父類工廠方法,給出創(chuàng)建具體產(chǎn)品類型的實(shí)現(xiàn)蜘矢,最后狂男,客戶端只需調(diào)用具體創(chuàng)建者中的方法即可得到所需的產(chǎn)品。
具體例子詳見(jiàn)推薦博客工廠方法模式
BaseViewHolder的封裝
/**
* 封裝的 viewholder 用于獲取各個(gè) item 上的控件 采用集合存儲(chǔ)取過(guò)的控件
*/
public class BaseViewHolder extends RecyclerView.ViewHolder {
protected View itemView;//每個(gè)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
來(lái)使用岖食,因此你無(wú)需在每個(gè)適配器里面再定義內(nèi)部類 MyViewHolder
。BaseViewHolder
可以在構(gòu)造器中獲取到每個(gè) item
的 itemView
舞吭,然后就可以定義一個(gè)快速獲取 itemView
上各個(gè)子控件的方法 getView
,以后在適配器中獲取 item
子控件能夠隨時(shí)調(diào)用此方法.
BetterViewHolder
public abstract class BetterViewHolder<T> extends BaseViewHolder {
public BetterViewHolder(View itemView) {
super(itemView);
}
/**
* 綁定 item 的數(shù)據(jù)
* @param t 每個(gè)item的實(shí)體引用
*/
public abstract void bindDataToItem(T t,int position);
}
BetterViewHolder
聲明為抽象類型泡垃,在 BaseViewHolder
基礎(chǔ)上再次封裝了一層。主要定義了一個(gè)抽象方法用于實(shí)現(xiàn)適配器中 onBindViewHolder(T t羡鸥,int position)
的功能蔑穴。但由于子類的實(shí)體類型不可確定,故需要借助泛型技巧,定義T來(lái)表示子類的泛型惧浴。因此存和,子類只需繼承該類,并指定子類所需的實(shí)體類型即可。
實(shí)例解析
-
本例中定義了四種
item
布局類型,如下圖所示
布局代碼見(jiàn)底部源碼
- 定義訪問(wèn)者
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);
}
- 定義被訪問(wèn)者
Visitable
/*
*定義抽象的被訪問(wèn)者 type方法捐腿,用來(lái)接收/引用一個(gè)抽象訪問(wèn)者對(duì)象,以便利用這個(gè)對(duì)象進(jìn)行操作;
*/
public abstract class Visitable {
public abstract int type(TypeFactory factory);
}
-
定義四個(gè)實(shí)體類并繼承
Visitable
/** * Created by hrx on 2017/4/30. * 具體的被訪問(wèn)者 * 實(shí)現(xiàn)type抽象方法纵朋,通過(guò)傳入的具體訪問(wèn)者參數(shù)、 * 調(diào)用具體訪問(wèn)者對(duì)該對(duì)象的訪問(wèn)操作方法實(shí)現(xiàn)訪問(wèn)邏輯; * 比如這就是利用抽象訪問(wèn)者 TypeFactory 引用來(lái)調(diào)用操作方法獲取對(duì)應(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); } }
-
具體訪問(wèn)者
TypeFactoryList
/* * 具體的訪問(wèn)者實(shí)現(xiàn)了抽象訪問(wèn)者的方法 * 同時(shí) onCreateViewHolder 也是利用工廠方法模式創(chuàng)建了各個(gè) item 的 viewholder 實(shí)例 */ public class TypeFactoryList extends TypeFactory { //聲明每個(gè)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)建細(xì)節(jié)已經(jīng)抽象到 TypeFactoryList 中去實(shí)現(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); } //此處的實(shí)現(xiàn)交由 BetterViewHolder 的各個(gè)子類去實(shí)現(xiàn)茄袖,故此處 Java 會(huì)根據(jù)相應(yīng)的子類去獲取其下實(shí)現(xiàn)的 bindDataToItem(),利用JAVA動(dòng)態(tài)分派而無(wú)需進(jìn)行類型檢查 @Override public void onBindViewHolder(BetterViewHolder holder, int position) { holder.bindDataToItem(mVisitables.get(position),position); } //此處即代表訪問(wèn)者模式中的客戶端調(diào)用被訪問(wèn)者的 type()操软,進(jìn)行訪問(wèn)操作獲取其布局 id @Override public int getItemViewType(int position) { return mVisitables.get(position).type(factory); } @Override public int getItemCount() { return mVisitables.size(); } }
該適配器的數(shù)據(jù)就是得到訪問(wèn)者模式中的對(duì)象結(jié)構(gòu) Object Structure
中存儲(chǔ)的被訪問(wèn)者集合,對(duì)應(yīng)到例子中就是 mVisitables
集合绞佩。同時(shí)需要一個(gè)訪問(wèn)者引用,以便該適配器(客戶端)能夠利用這個(gè)引用獲取每個(gè)被訪問(wèn)者關(guān)聯(lián)到的布局Id(利用該引用執(zhí)行某些操作)寺鸥,如mVisitables.ge(position).type(factory)
可以獲取到每個(gè)被訪問(wèn)者關(guān)聯(lián)到的布局 id
。
-
四個(gè)
BetterViewHolder
的子類四個(gè)子類代表了其對(duì)應(yīng)
item
的ViewHolder
,可以在該類上實(shí)現(xiàn)item
上的操作品山,比如點(diǎn)擊事件,設(shè)置item
上子控件的所有數(shù)據(jù)等烤低。
代碼見(jiàn)底部源碼
-
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);
//默認(rèn)4列
final GridLayoutManager manager = new GridLayoutManager(this, 4);
//此方法定義每個(gè)item占幾列,有點(diǎn)類似線性布局的權(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<>();
//按布局的順序依次加入各個(gè)被訪問(wèn)者
Banner banner = new Banner();
mVisitable.add(banner);
Category category = new Category();
mVisitable.add(category);
//加入4個(gè)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
集合中被訪問(wèn)者的加入順序即代表了最終顯示出來(lái)的順序肘交,并且集合中的每個(gè)元素僅能代表其中一個(gè)item
,意味著如果你要重復(fù)該item
就要重復(fù)聲明一個(gè)實(shí)體再加入集合中扑馁。
總結(jié)
利用訪問(wèn)者模式和工廠方法模式大大解耦了上述復(fù)雜布局的實(shí)現(xiàn)過(guò)程涯呻,同時(shí)可擴(kuò)展性大大提高。如果往后還需修改布局腻要,只需修改對(duì)應(yīng) item
的布局文件和數(shù)據(jù)的綁定复罐。而若是增加 item
,那么只需定義新的實(shí)體加入被訪問(wèn)者集合中雄家,同時(shí)編寫布局文件及對(duì)應(yīng)的 ViewHolder
實(shí)現(xiàn)即可效诅。這樣一來(lái),不同 item
間就不會(huì)相互影響趟济,變得易維護(hù)和易擴(kuò)展乱投,相信你學(xué)會(huì)之后,一定會(huì)愛(ài)上此法顷编。
最后戚炫,謝謝你看到這里,歡迎交流意見(jiàn)媳纬。
感謝
[譯]關(guān)于 Android Adapter双肤,你的實(shí)現(xiàn)方式可能一直都有問(wèn)題
Java設(shè)計(jì)模式之 訪問(wèn)者模式【Visitor Pattern】
Java設(shè)計(jì)模式之 工廠方法模式