基於AOP的一種RecyclerView複雜樓層開發框架,支持組件化,全局樓層打通,MVP等高拓展性功能

前言

RecyclerView做爲Google替代ListView的一個組件,其強大的拓展性和性能,如今已經成爲無數App核心頁面的主體框架。RecyclerView的開發模式通常來講都是多Type類型的ViewHolder——後面就稱爲樓層(感受很形象)。可是使用多了,許多問題就暴露出來了,常常考慮有這麼幾個問題:java

    1. 如何更便捷的使用Adapter和ViewHolder的開發模式?
    1. 如何和他人的樓層作到樓層的複用?
    1. 如何作到全局樓層的打通?
    1. 樓層自己如何作到邏輯閉合,作到MVP的組件化模式?

功能特性

  • 基於編譯期註解,不影響性能
  • 使用簡單,樓層耦合度低
  • 代碼侵入性低
  • 支持全局樓層打通,多人樓層打通
  • 樓層支持點對點MVP模式
  • 事件中心模式,樓層只是事件的傳遞者。
  • 生命週期監聽,支持邏輯的生命週期感知。
  • 豐富的API,支持多方面拓展。
  • 提供組件化工程使用方案
  • 不用每次再寫Adapter了~

項目地址

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;
}

複製代碼

一.單樣式列表

1.定義樓層(支持三種模式)
  • 繼承Component類型
@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() {
    }
}

複製代碼
  • 繼承原生ViewHolder類型
@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() {
    }
}
複製代碼
  • 自定義View類型
@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

  1. value:樓層的惟一標示,int型
  2. layout:樓層的佈局文件
  3. 繼承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()
    }
複製代碼
2.定義Model
@BindType(ComponentId.SIMPLE)
public class SimpleModel {
    
}
複製代碼

BindType:當是單樣式時,model直接註解對應的樓層的惟一標示,int型框架

3.綁定RecyclerView
@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.多樣式判斷邏輯(兩種方式)函數

2.1 Model實現HandlerType接口處理邏輯
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類,一下就能夠找到相關邏輯。

2.2 實現IModerBinder接口自定義處理類

一款好的框架確定是對修改關閉,對拓展開放的,當咱們認爲放到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,合併代碼時解決衝突每每是很大的問題,而且不可能全部的樓層都是全局打通的類型,因此這裏提供一種我的開發模式。

用法

  • 1.使用attach註解,綁定對應class
@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() {

    }
}
複製代碼
  • 2.調用SlotContext.attachRule綁定對應的Class
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的時候才能綁定,構造函數的時候,事件中心對象尚未注入進來。

    1. ViewHolder實現InjectCallback接口,在onBind生命週期就能夠拿到事件中心對象。
    1. 經過View.setTag,將事件類型(int型等,惟一性)和相關須要的數據傳出。

事件中心的思想就是:ViewHolder單純的只傳遞事件,徹底由數據驅動事件,View不感知事件類型,也就是說,這個ViewHolder的事件是可變的

MVP的拆分

關於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;
    }
}
複製代碼

對應的須要三步:

    1. slotContext.registerLogic(IPresenter presener),這裏IPresenter只是一個空接口,用於代表這是一個邏輯層的類。
    1. 在ViewHolder利用@ILogic註解對應的P的Class
    1. ViewHolder實現IPresenterBind接口,注入註冊給SlotContext對應的Presenter.

生命週期感知

不管是Presenter仍是任何其餘類,當脫離的Activity,對於生命週期的感知時很是重要的,因此SlotContext提供的有兩個API

pushLife(ILifeCycle lifeCycle)
pushGC(IGC gc)
複製代碼

須要感知生命週期,或者僅僅感知OnDestroy的類,只需實現相應的接口,並利用api註冊觀察者便可。

MIX模式,多樓層打通

對於多樓層打通,咱們須要利用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

ToolKitBuilder的構造函數

public ToolKitBuilder(Context context, List<T> data)
public ToolKitBuilder(Context context)
複製代碼

ToolKitBuilder的API

方法名 描述 備註
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對象

SlotContext的構造函數

public SlotContext(Context context, List<T> data)
public SlotContext(ToolKitBuilder<T> builder)
複製代碼

SlotContext的API

方法名 描述 備註
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提意見~

相關文章
相關標籤/搜索