前言
用MVP模式開發(fā)安卓相信大家也應(yīng)該不是很陌生了徽惋。各種各樣的mvp開發(fā)模式嘱能,框架也是層出不窮。之前寫過一篇《MVP的一種新的嘗試》 赫段,然后我基于這種模式(activity作為presenters)做了一個(gè)可能問題還是很多的開發(fā)框架:項(xiàng)目地址 乌询。并且根據(jù)這個(gè)框架在前幾天結(jié)合rxjava,retrofit,dagger做了一個(gè)挺簡單的demo app:githubQuery 榜贴。我在想做這套框架之后意外的發(fā)現(xiàn)一個(gè)大牛做的框架和我是一樣的思想!都是借鑒了這篇文章:《借鑒文章》 妹田。做的也是大同小異了竣灌,順便也借鑒了不少,還借鑒了另一個(gè)學(xué)長把a(bǔ)dapter和viewholder解耦的思想秆麸。
傳統(tǒng)mvp模式的不足
如果把a(bǔ)ctivity作為view初嘹,那么如果activity異常銷毀,那么恢復(fù)數(shù)據(jù)就是個(gè)問題了沮趣。并且還必須要把presenter和view生命周期同步屯烦。如果使用bundle,這是就直接破壞了mvp的設(shè)計(jì)模式房铭,因?yàn)檫@樣難免會(huì)把v和m耦合在一起驻龟。而將activity作為view,可以很方便的使用bundle和model層的數(shù)據(jù)進(jìn)行異掣追耍恢復(fù)翁狐。
context以及各種安卓系統(tǒng)服務(wù),使用intent等等凌蔬。
-
開始想用dagger進(jìn)一步把v-p-m解耦露懒,后來發(fā)現(xiàn)不用了闯冷,,
具體不足請(qǐng)看前言中的借鑒文章懈词!
EasyMVP
介紹
1.使用泛型來使view和presenter解耦蛇耀,通過編譯期間從子activity傳遞過來的view類型反射獲得具體view類型。并且框架完成activity的視圖渲染坎弯,并且提供同步activity生命周期的函數(shù)供開發(fā)者使用纺涤。
2.默認(rèn)使用butterknife作為view的依賴注入。
3.adapter作為presenter抠忘,并且與viewholder完全解耦撩炊,viewholder歸入view層。默認(rèn)使用recycle view崎脉。(別再提listview了)
BaseActivityPresenters
public abstract class BaseActivityPresenter<V extends IView> extends AppCompatActivity{
protected V v;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
//通過注解獲得對(duì)應(yīng)的view的實(shí)例
v = getRootViewClass().newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//調(diào)用view層中生成根視圖的函數(shù)
v.creatView(getLayoutInflater(), null);
//調(diào)用view層初始化控件的函數(shù)衰抑,這里默認(rèn)使用butterkinfe了。
v.initView();
//視圖渲染
setContentView(v.getRootView());
//拋出跟onCreat()函數(shù)同步的函數(shù)給開發(fā)者
inCreat(savedInstanceState);
}
//接觸綁定
@Override
protected void onDestroy() {
super.onDestroy();
v.removeView();
v = null;
inDestory();
}
public abstract Class<V> getRootViewClass();
public abstract void inCreat(Bundle savedInstanceState);
public abstract void inDestory();
public void inPause(){}
public void inRestart(){}
public void inStop(){}
public void inResume(){}
public void inStart(){}
@Override
protected void onStart() {
super.onStart();
inStart();
}
我已經(jīng)在代碼里面添加了一些注釋荧嵌。**框架已經(jīng)默認(rèn)了使用butterkinfe**呛踊,要避免重復(fù)依賴。再來看view層的框架代碼啦撮。這樣做其實(shí)是可以看成是在抽出一個(gè)BaseActivity谭网,把一些操作黑箱化。
IView
public interface IView{
void creatView(LayoutInflater inflater, ViewGroup parent);
View getRootView();
int getRootViewId();
void initView();
//調(diào)用butterkinfe的unbind()方法
void removeView();
}
確定了幾個(gè)view代理層最基本的操作赃春。
BaseViewImpl
/**
* Created by Zane on 15/12/18.
* 將view加載的過程寫在抽象類愉择,做到代碼復(fù)用。
*/
public abstract class BaseViewImpl implements IView{
protected View view;
protected final SparseArray<View> mViews = new SparseArray<View>();
//根據(jù)activity傳遞過來的參數(shù)和具體view類傳遞過來的id生成根視圖织中。
@Override
final public void creatView(LayoutInflater inflater, ViewGroup parent) {
int resourceId = getRootViewId();
if (resourceId == 0){
throw new RuntimeException("rootview's id can't be null");
}
view = inflater.inflate(resourceId, parent, false);
}
//這就是baseactivitypresenter中調(diào)用用來渲染試圖的方法锥涕。
@Override
final public View getRootView() {
return view;
}
//由子類去重寫
@Override
public abstract int getRootViewId();
//添加注解view方式
@Override
final public void initView() {
ButterKnife.bind(this, view);
}
@Override
final public void removeView() {
ButterKnife.unbind(this);
}
BaseListViewHolder
這個(gè)是可以和baseviewimpl對(duì)應(yīng)的,是一個(gè)viewholder的抽象父類狭吼。用的同樣的方法通過中間層做到view holder和adapter解耦层坠。區(qū)別不是太大!
/**
* Created by Zane on 15/12/18.
* 這個(gè)中間的base層用來做到viewholder與adapter的解耦刁笙。
*/
public abstract class BaseListViewHolderImpl<M extends Object> extends RecyclerView.ViewHolder{
public BaseListViewHolderImpl(View itemView) {
super(itemView);
}
//生成viewholder的構(gòu)造方法破花。
public BaseListViewHolderImpl(ViewGroup parent, @LayoutRes int res){
super(LayoutInflater.from(parent.getContext()).inflate(res, parent, false));
}
public abstract void initView();
public abstract void setData(M data);
protected <T extends View> T $(@IdRes int id) {
return (T) itemView.findViewById(id);
}
}
我繼承了recycle view的view holder,并且暴露出來一個(gè)構(gòu)造函數(shù)疲吸,相當(dāng)于代理層的構(gòu)造函數(shù)座每,然后函數(shù)里面調(diào)用了recycle view中viewholder的構(gòu)造方法,以此來生成最終的view holder摘悴。
而M則是recycle view展示的model的數(shù)據(jù)類型峭梳。以此保證view holder和adapter數(shù)據(jù)類型的一致。方便下面的操作蹂喻。
setData()方法的設(shè)計(jì)有點(diǎn)像接口回調(diào)葱椭,在holder的子類里面實(shí)現(xiàn)這個(gè)函數(shù)捂寿,在adapter里面進(jìn)行回調(diào)。
BaseListAdapterPresenter
我的這個(gè)框架的啟蒙文章里面也說挫以,適配器是可以作為presenter的者蠕。這個(gè)抽象類的主要任務(wù)就是調(diào)用具體holder子類的構(gòu)造函數(shù)窃祝,并且傳遞數(shù)據(jù)給setData()方法掐松,形成接口回調(diào)。
/**
* Created by Zane on 15/12/18.
*/
public abstract class BaseListAdapterPresenter<M extends Object> extends RecyclerView.Adapter<BaseListViewHolderImpl>{
protected Context mContext;
protected List<M> mDatas;
public BaseListAdapterPresenter(Context mContext){
this(mContext, null);
}
//根據(jù)具體adapter子類構(gòu)造函數(shù)獲得的數(shù)據(jù)去初始化這里的數(shù)據(jù)粪小。
public BaseListAdapterPresenter(Context mContext, List<M> mDatas){
this.mContext = mContext;
this.mDatas = mDatas;
}
@Override
public BaseListViewHolderImpl onCreateViewHolder(ViewGroup parent, int viewType) {
return OnCreatViewHolder(parent, viewType);
}
//給具體adapter子類去實(shí)現(xiàn)大磺,在子類里面調(diào)用具體的holder構(gòu)造函數(shù)
public abstract BaseListViewHolderImpl OnCreatViewHolder(ViewGroup parent, int viewType);
public M getItem(int position){
return mDatas.get(position);
}
@Override
public int getItemCount() {
return mDatas.size();
}
}
問題
剛開始這樣寫完了之后探膊,發(fā)現(xiàn)一個(gè)問題就是presenter里面難免會(huì)需要view層的控件杠愧。比如點(diǎn)擊一個(gè)button實(shí)現(xiàn)頁面跳轉(zhuǎn),首先跳轉(zhuǎn)功能當(dāng)然要寫在presenter逞壁,這就是跟傳統(tǒng)mvp不同的一點(diǎn)。然后我就在想如果把view的控件一個(gè)個(gè)的暴露給presenter腌闯,但是如果需求量一大后果不堪設(shè)想绳瘟。。然后我就看到那個(gè)大牛的框架用一個(gè)方法解決了這個(gè)問題:
protected final SparseArray<View> mViews = new SparseArray<View>();
final public <T extends View> T bindView(int id) {
T view2 = (T) mViews.get(id);
if (view2 == null) {
view2 = $(id);
mViews.put(id, view2);
}
return view2;
}
final public <T extends View> T get(int id) {
return (T) bindView(id);
}
final protected <T extends View> T $(@IdRes int id) {
return (T) view.findViewById(id);
}
//暴露監(jiān)聽函數(shù)
final public void setOnClickListener(View.OnClickListener listener, int... ids) {
if (ids == null) {
return;
}
for (int id : ids) {
get(id).setOnClickListener(listener);
}
}
他用了一個(gè)可變長度的數(shù)組來存儲(chǔ)控件姿骏。首先你可以用get(int id)方法或者$(int id)方法(建議就用get()糖声,因?yàn)椴槐刂貜?fù)去findview)去在presenter中獲得控件,但是不到萬不得已別這樣做分瘦,不然就破壞了mvp的設(shè)計(jì)模式蘸泻。再就是很佩服他的setOnClickListener(View.OnClickListener listener, int... ids)方法。完美的解決了在presenter不獲得控件還能完成監(jiān)聽這個(gè)問題嘲玫。
使用
簡單demo:
/**
* Created by Zane on 16/1/27.
*/
public class MainView2 extends BaseViewImpl {
@Bind(R.id.button)
Button button;
@Bind(R.id.edit)
EditText edit;
@Override
public int getRootViewId() {
return R.layout.activity_2;
}
public void setText(String test){
edit.setText(test);
}
}
這是一個(gè)view悦施,看到了,由于框架做了butterkinfe綁定去团,所以直接@Bind()歼争!然后返回自己對(duì)應(yīng)的xml文件id。
/**
* Created by Zane on 16/1/27.
*/
public class MainActivity2 extends BaseActivityPresenter<MainView2>{
@Override
public Class<MainView2> getRootViewClass() {
return MainView2.class;
}
@Override
public void inCreat(Bundle bundle) {
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity2.this, MainActivity.class));
}
}, R.id.button);
}
@Override
public void inDestory() {
}
}
返回對(duì)應(yīng)view的class渗勘,并且完成了一次點(diǎn)擊button跳轉(zhuǎn)頁面沐绒。
/**
* Created by Zane on 15/12/20.
*/
public class MainListViewHolder extends BaseListViewHolderImpl<data> {
TextView mTextView;
public MainListViewHolder(ViewGroup parent) {
super(parent, R.layout.listview_item_layout);
initView();
}
@Override
public void initView() {
mTextView = $(R.id.item_text);
}
@Override
public void setData(data data) {
mTextView.setText(data.getDatas());
}
}
viewholer中不能使用butterknife默認(rèn)綁定,這個(gè)問題我還沒想到辦法怎么解決旺坠。乔遮。不過直接用$()函數(shù)也是不錯(cuò)的∪∪校看到重寫了父類的抽象方法蹋肮,而這個(gè)函數(shù)在adapter里面被回調(diào)出刷。
/**
* Created by Zane on 15/12/20.
*/
public class MyRecycleviewAdapter extends BaseListAdapterPresenter<data>{
public MyRecycleviewAdapter(Context mContext, List<data> datas){
super(mContext, datas);
}
@Override
public BaseListViewHolderImpl OnCreatViewHolder(ViewGroup parent, int viewType) {
return new MainListViewHolder(parent);
}
@Override
public void onBindViewHolder(BaseListViewHolderImpl holder, int position) {
holder.setData(getItem(position));
}
}
調(diào)用了父類的構(gòu)造函數(shù),返回了具體holder子類坯辩。大家會(huì)發(fā)現(xiàn)我并沒有把holder和adapter完全解耦馁龟。對(duì),我只是把item和adapter解耦了漆魔。使item的邏輯由自己管理坷檩,并且將holder分離出來并入view層。這塊是借鑒了學(xué)長一個(gè)庫的經(jīng)驗(yàn):項(xiàng)目地址:EasyRecycleView
總結(jié)
這是我第一次去嘗試寫一個(gè)框架出來改抡,并且借鑒了很多別人的思想。而且實(shí)踐的次數(shù)不多阿纤,算是自己邁出的第一步吧句灌,以后肯定會(huì)遇到越來越多的問題,但是很值得欠拾。突然發(fā)現(xiàn)我這么一個(gè)小小的框架都需要想很多問題资昧,那些牛逼框架能設(shè)計(jì)出來真是不容易。
未經(jīng)博主同意践惑,不得轉(zhuǎn)載該篇文章