RecyclerView做爲Google替代ListView的一個組件,其強大的拓展性和性能,如今已經成爲無數App核心頁面的主體框架。RecyclerView的開發模式通常來講都是多Type類型的ViewHolder——後面就稱爲樓層(感受很形象)。可是使用多了,許多問題就暴露出來了,常常考慮有這麼幾個問題:java
EMvpgit
歡迎Star👏~ 歡迎提issue討論~github
這裏就介紹一下基於本身對於RecyclerView的理解,開發的一款基於AOP的,適用於多樓層模式的RecyclerView的開發框架。設計模式
@Documented()
// 表示是基於編譯時註解的
@Retention(RetentionPolicy.CLASS)
// 表示能夠做用於成員變量,類、接口
@Target(ElementType.TYPE)
public @interface ComponentType {
//ComponentId
int value() default -1;
//LayoutId,當爲ViewHolder類型須要
int layout() default -1;
//組件化項目時,註解父View,經過LayoutInflater建立佈局
Class view() default Object.class;
//是否利用反射建立,默認打開的(複雜的,性能相關的,數量大的固然建議關閉咯)
boolean autoCreate() default true;
//樓層綁定的類,經過類來尋找樓層的可用範圍
Class attach() default Object.class;
}
複製代碼
@ComponentType(
value = ComponentId.SIMPLE,
layout = R.layout.single_text
)
public class SimpleVH extends Component {
public SimpleVH(Context context, View itemView) {
super(context, itemView);
}
@Override
public void onBind(int pos, Object item) {
}
@Override
public void onUnBind() {
}
}
複製代碼
@ComponentType(
value = PersonId.VIEWHOLDER,
layout = R.layout.person_item_layout
)
public class PersonVH extends RecyclerView.ViewHolder implements IComponentBind<PersonModel> {
private TextView tvName;
public PersonVH(View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tv_name);
}
@Override
public void onBind(int pos, PersonModel item) {
tvName.setText(item.name);
}
@Override
public void onUnBind() {
}
}
複製代碼
@ComponentType(PersonId.CUSTOM)
public class CustomView extends LinearLayout implements IComponentBind<PersonModel> {
public CustomView(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.cutom_view_vh, this, true);
setBackgroundColor(Color.BLACK);
}
@Override
public void onBind(int pos, PersonModel item) {
}
@Override
public void onUnBind() {
}
}
複製代碼
很清晰,不用再每次在複雜的if else
中尋找本身樓層對應的佈局文件。(熟悉的人應該都懂) 注意:api
- value:樓層的惟一標示,int型
- layout:樓層的佈局文件
- 繼承ViewHolder和自定義View類型須要實現
IComponentBind
接口便可
對於R文件不是常量在組件化時遇到的問題的解決方案 Wiki緩存
這裏沒有選用butterknife將R文件複製一份成R2的方式,我我的感受不是特別優雅,最終我選擇的是在註解中增長一種View類型的註解,能夠在註解中註解父View的Class,而後在構造函數經過LayoutInflater加入佈局文件。bash
@ComponentType(
value = ComponetId.BANNER,
view = FrameLayout.class
)
public BannerVH(Context context, View itemView) {
super(context, itemView);
fgContainer = (FrameLayout) itemView;
//再利用LayoutInflater
LayoutInflater.from(context).inflate()
}
複製代碼
@BindType(ComponentId.SIMPLE)
public class SimpleModel {
}
複製代碼
BindType:當是單樣式時,model直接註解對應的樓層的惟一標示,int型框架
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.common_layout);
mRcy = findViewById(R.id.rcy);
mRcy.setLayoutManager(new LinearLayoutManager(this));
new ToolKitBuilder<>(this, mData).build().bind(mRcy);
}
複製代碼
使用對應的API,利用build()方法構建SlotsContext實體最後利用bind()
方法綁定ReyclerView.ide
1.定義ViewHolder(同前一步) 2.多樣式判斷邏輯(兩種方式)函數
public class CommonModel implements HandlerType {
public int pos;
public String tips;
public String eventId;
@Override
public int handlerType() {
if (pos > 8) {
pos = pos % 8;
}
switch (pos) {
case 1:
return ComponentId.VRCY;
case 3:
return ComponentId.DIVIDER;
case 4:
return ComponentId.WEBVIEW;
case 5:
return ComponentId.TEXT_IMG;
case 6:
return ComponentId.IMAGE_TWO_VH;
case 7:
return ComponentId.IMAGE_VH;
case 8:
return ComponentId.USER_INFO_LAYOUT;
}
return ComponentId.VRCY;
}
}
複製代碼
返回定義的ItemViewType,這裏封裝在Model內部,是因爲平時咱們老是將java中的Model看成一個JavaBean,而致使咱們賦予Model的職責過於輕,因此就會出現更多的其實和Model緊密相關的邏輯放到了Activity,Presenter或者別的地方,可是其實當咱們將Model看成數據層來看待,其實能夠將許多與Model緊密相關的邏輯放到Model中,這樣咱們其實單模塊的邏輯內聚度就很高,便於咱們理解。 (這裏思路其實來源於IOS開發中的胖Model的概念,你們能夠Goolge一下)
好處:當咱們須要肯定樓層之間和Model的關係,直接按住ctrl,進入Model類,一下就能夠找到相關邏輯。
一款好的框架確定是對修改關閉,對拓展開放的,當咱們認爲放到Model中處理過於粗暴,或者Model中已經有過多的邏輯了,咱們也能夠將邏輯抽出來,實現IModerBinder接口。
public interface IModerBinder<T> {
int getItemType(int pos, T t);
}
複製代碼
對應的利用ToolKitBuilder.setModerBinder(IModerBinder<T> moderBinder)
構建便可。例如:
.setModerBinder(new ModelBinder<PersonModel>() {
@Override
protected int bindItemType(int pos, PersonModel obj) {
//處理Type的相關邏輯
return type;
}
})
複製代碼
當涉及到大型項目時,多人協做每每是一個問題,當全部人都維護一套ComponentId,合併代碼時解決衝突每每是很大的問題,而且不可能全部的樓層都是全局打通的類型,因此這裏提供一種我的開發模式。
@ComponentType(
value = PersonId.VIEWHOLDER,
layout = R.layout.person_item_layout,
//class類型,對應到映射表的key
attach = PersonModel.class
)
public class PersonVH extends RecyclerView.ViewHolder implements IComponentBind<PersonModel> {
private TextView tvName;
public PersonVH(View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tv_name);
}
@Override
public void onBind(int pos, PersonModel item) {
//tvName.findViewById(R.id.tv_name);
tvName.setText(item.name);
}
@Override
public void onUnBind() {
}
}
複製代碼
SlotContext slotContext =
new ToolKitBuilder<PersonModel>(this)
//註冊綁定的類型,對應獲取映射表
.attachRule(PersonModel.class).build();
複製代碼
項目利用Build模式構建SlotContext實體,SlotContext原理基於Android中的Context思想,做爲一個全局代理的上下文對象,經過SlotContext,咱們能夠獲取對應的類,進而實現對應類的獲取和通訊。
框架自己利用反射進行建立,內部利用LruCache
對反射對構造器進行緩存,優化反射性能。若是想要避免反射對建立,也是能夠自定義建立過程。
@ComponentType(
value = PersonId.INNER,
view = TextView.class,
//註解不使用反射
autoCreate = false
)
public static class InnerVH extends RecyclerView.ViewHolder implements IComponentBind<PersonModel> {
....
}
複製代碼
能夠將不須要反射建立對ViewHolder的autoCreate=false
,而後經過ToolKitBuilder. setComponentFactory()
自定義建立過程。 具體方式->Wiki
事件中心其實本質就是一個繼承於View.OnClickListener
的類,全部和ViewHolder自己無關的事件,統一傳遞給事件中心,再由事件中心處理,對應於一條準則:
ViewHolder只是一個專一於展現UI的殼,只作事件的傳遞者,不作事件的處理者。
使用方式:
@ComponentType(
value = ComponetId.SINGLE_TEXT,
layout = R.layout.single_text
)
public class TextVH extends Component<Text> implements InjectCallback {
private TextView tv;
private View.OnClickListener onClickListener;
public TextVH(Context context, View itemView) {
super(context, itemView);
tv = (TextView) itemView;
}
@Override
public void onBind(int pos, Text item) {
tv.setText(item.title);
//此處全部的數據和事件類型經過setTag傳出
tv.setTag(item.eventId);
tv.setOnClickListener(onClickListener);
}
@Override
public void onUnBind() {
}
@Override
public void injectCallback(View.OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
}
複製代碼
仿照依賴注入的思想,只不過代碼侵入性沒有那麼強,固然只能在onBind的時候才能綁定,構造函數的時候,事件中心對象尚未注入進來。
事件中心的思想就是:ViewHolder單純的只傳遞事件,徹底由數據驅動事件,View不感知事件類型,也就是說,這個ViewHolder的事件是可變的!
關於MVP是什麼這裏就很少講了,這裏講一講MVP的拆分,常規的MVP咱們常常作的就是一個P完成全部的邏輯,可是這時帶來的問題就時P層過於大,這時個人理解就是對P進行拆分,具體拆分的粒度要根據不一樣的業務場景來區分(這個就比較考驗開發者對於設計模式的理解)。而ViewHolder自身能夠完成一套MVP體系,想想,當一個特殊的樓層,涉及複雜的業務邏輯,這時徹底將這個樓層拆分紅MVP模式,這時其餘頁面須要使用的時候,只須要new對應的MVP便可。
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
slotContext = new ToolKitBuilder<>(this, mData).build();
//1.註冊對應的邏輯類
slotContext.registerLogic(new CommonLogic(slotContext));
...
}
@ComponentType(value = ComponentId.TEXT_IMG)
//2.註解對應的邏輯類
@ILogic(CommonLogic.class)
//3.實現IPresenterBind接口
public class TextImgLayout extends LinearLayout implements IComponentBind<CommonModel>,IPresenterBind<CommonLogic> {
private View root;
private TextView tvInfo;
private CommonLogic logic;
...
@Override
public void onBind(int pos, CommonModel item) {
tvInfo.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (logic != null) {
//對應的P,處理業務邏輯
logic.pageTransfer();
}
}
});
}
...
@Override
public void injectPresenter(CommonLogic commonLogic) {
this.logic = commonLogic;
}
}
複製代碼
對應的須要三步:
slotContext.registerLogic(IPresenter presener)
,這裏IPresenter只是一個空接口,用於代表這是一個邏輯層的類。不管是Presenter仍是任何其餘類,當脫離的Activity,對於生命週期的感知時很是重要的,因此SlotContext提供的有兩個API
pushLife(ILifeCycle lifeCycle)
pushGC(IGC gc)
複製代碼
須要感知生命週期,或者僅僅感知OnDestroy的類,只需實現相應的接口,並利用api註冊觀察者便可。
對於多樓層打通,咱們須要利用ToolKitBuilder實現IMixStrategy策略。
public interface IMixStrategy<T> {
//經過type獲得真正的映射表中的ComponentId
int getComponentId(int type);
//經過Type肯定對應的映射表
Class<?> attachClass(int type);
//傳入ViewHolder的Bind中的實體類
Object getBindItem(int pos, T t);
}
複製代碼
具體方案->Wiki
public ToolKitBuilder(Context context, List<T> data)
public ToolKitBuilder(Context context)
複製代碼
方法名 | 描述 | 備註 |
---|---|---|
setData(List data) | 設置綁定的數據集 | 空對象,對應的構造的size=0 |
setModerBinder(IModerBinder moderBinder) | 處理多樣式時Model對應的Type | 處理優先級優先於HandlerType和註解BindType |
setEventCenter(View.OnClickListener onClickListener) | 設置事件中心 | ViewHolder的事件綁定後都會回調到這個事件中心 |
setComponentFactory(CustomFactory componentFactory) | 設置自定義建立ViewHolder的工廠 | 能夠自定義建立三種類型 |
setMixStrategy(IMixStrategy mixStrategy) | 設置混合模式處理策略 | 多人樓層打通 |
attachRule(Class<?> clazz) | 註冊樓層映射表 | 我的模式和混合模式 |
SlotContext build() | 構建出SlotContext對象 |
public SlotContext(Context context, List<T> data)
public SlotContext(ToolKitBuilder<T> builder)
複製代碼
方法名 | 描述 | 備註 |
---|---|---|
Context getContext() | 獲取Context對象 | |
setData(List data) | 綁定數據集 | 這裏不會刷新數據,僅僅是設置 |
notifyDataSetChanged() | 刷新數據 | 只提供了全局刷新的方式,局部刷新能夠經過獲取Adapter使用 |
attachRule(Class<?> clazz) | 註冊樓層映射表 | 我的模式和混合模式 |
registerLogic(IPresent logic) | 註冊Presenter邏輯 | 可註冊多個,須要實現IPresenter空接口 |
obtainLogic(Class<?> clazz) | 獲取對應註冊的Presenter實例 | 以class做爲key |
bind(RecyclerView rcy) | 綁定Adapter | 會從新建立Adapter並綁定 |
RecyclerView.Adapter getAdapter() | 獲取Adapter | |
pushLife(ILifeCycle lifeCycle) | 註冊任何對象監聽生命週期 | 實現ILifeCycler接口 |
pushGC(IGC gc) | 監聽Destroy生命週期 |
更多使用方式詳見Wiki
Python自動生成10000個java類使用APT註解後引起的問題
項目地址:EMvp 歡迎Star👏 歡迎你們提issues提意見~