MVP設(shè)計(jì)模式

前言

以前在學(xué)習(xí)Android的時(shí)候,第一次接觸設(shè)計(jì)模式,就是MVC。當(dāng)時(shí),寫(xiě)的代碼不多,聽(tīng)得懵懵懂懂。在后來(lái),深切感受到是在第一次組隊(duì)寫(xiě)項(xiàng)目的時(shí)候,由于時(shí)間緊迫,前期我們就做了分工,我當(dāng)時(shí)的任務(wù)就是搞定接口文檔,并封裝一個(gè)網(wǎng)絡(luò)請(qǐng)求類(lèi),別的組員就暫時(shí)通過(guò)假數(shù)據(jù)做著自己的模塊。

突然,我就感覺(jué)到其實(shí) APP 很簡(jiǎn)單,就是UI+數(shù)據(jù),沒(méi)有真正數(shù)據(jù)的 APP 是死的,一旦有了數(shù)據(jù)與之互動(dòng),那么它就變活了,當(dāng)然了,活得這么樣又是另外一碼事。那么,一手UI,一手?jǐn)?shù)據(jù),UI如何顯示,數(shù)據(jù)什么時(shí)候更新,什么時(shí)候更新UI等等中間的互動(dòng)就需要一個(gè)橋梁。

MVC

MVC, Model View Controller,這里的橋梁就是Controller,簡(jiǎn)單來(lái)說(shuō)就是通過(guò) Controller 的控制區(qū)操作 Model 層的數(shù)據(jù),然后返回給 View 層顯示。

mvc設(shè)計(jì)模式.png
  • Model : javaBean 業(yè)務(wù)邏輯相關(guān)
  • View : xml文件
  • Controller : activity fragment

在這里看 View 層,它是 xml 文件,它僅僅是一些布局文件,控制能力基本沒(méi)有,如果想要去控制其中的控件隱藏/顯示,只能將代碼寫(xiě)到 activity 中,造成了 activity 既是 View 層也是 Controller 層的窘境。也就造成了 android中大部分app用的是view-model 。

Most of the modern Android applications just use View-Model architecture,everything is connected with Activity.

這就導(dǎo)致了,如果是一個(gè)邏輯很復(fù)雜的頁(yè)面,activity/fragment的代碼將相當(dāng)?shù)凝嫶笥纺[,維護(hù)起來(lái)也不方便。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);

    initRetrofit();
    initView();
    initEvent();
    initData();
}

private void initData() {
    getDatas(REFRESH_DATA);
}

private void initRetrofit() {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl("http://gank.io/")
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();
    mGankioService = retrofit.create(GankioService.class);
}

private void initEvent() {
    mSr.setOnRefreshListener(mOnRefreshListener);
}

private void initView() {
    mSr = (SwipeRecyclerView) findViewById(R.id.activity_swipe_recycler);
    /**設(shè)置布局管理器*/
    mLayoutManager = new GridLayoutManager(this, 3);
    mSr.setLayoutManager(mLayoutManager);
    /**設(shè)置adapter*/
    mMyadapter = new MyAdapter();
    mSr.setAdapter(mMyadapter);
}

class MyAdapter extends SwipeRecyclerView.Adapter<MyAdapter.MyViewHolder> {

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        MyViewHolder vh = new MyViewHolder(LayoutInflater.from(HomeActivity.this).inflate(R.layout.activity_recycler_item, parent, false));
        return vh;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        final String url = mDatas.get(position).url;

        Glide.with(holder.mImageView.getContext())
                .load(url)
                .centerCrop()
                .placeholder(R.color.app_primary_dark)
                .crossFade()
                .into(holder.mImageView);

        holder.mImageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(HomeActivity.this, BitmapActivity.class);
                intent.putExtra("url", url);
                startActivity(intent);
            }
        });
    }

    @Override
    public int getItemCount() {
        if (mDatas == null)
            return 0;
        return mDatas.size();
    }

    @Override
    protected void loadMoreDatas(RecyclerView recyclerView, int dx, int dy) {
        int position = mLayoutManager.findLastVisibleItemPosition();
        int itemCount = mMyadapter.getItemCount();
        if (position + 1 == itemCount) {
            getDatas(LOADMORE_DATA);
        }
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {

        ImageView mImageView;

        public MyViewHolder(View itemView) {
            super(itemView);
            mImageView = (ImageView) itemView.findViewById(R.id.item_imageview);
        }
    }

}

private void getDatas(final int action) {

    if (action == REFRESH_DATA) {
        page = 1;
    } else {
        page++;
    }

    mGankioService.getWelfare(50, page)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new Subscriber<BaseModel<List<Welfare>>>() {
                @Override
                public void onStart() {
                    mSr.mSwipeRefreshLayout.setRefreshing(true);
                }

                @Override
                public void onCompleted() {
                    mSr.mSwipeRefreshLayout.setRefreshing(false);
                }

                @Override
                public void onError(Throwable e) {
                    Log.i(">>>", "onError " + e.toString());
                }

                @Override
                public void onNext(BaseModel<List<Welfare>> listBaseModel) {
                    if (action == REFRESH_DATA) {
                        mDatas.clear();
                    }
                    mDatas.addAll(listBaseModel.results);
                    mMyadapter.notifyDataSetChanged();
                }
            });
}

private SwipeRecyclerView.OnRefreshListener mOnRefreshListener = new SwipeRecyclerView.OnRefreshListener() {
    @Override
    public void onRefresh() {
        getDatas(REFRESH_DATA);
    }
};

這是我之前用 mvc 寫(xiě)的gank.io其中一個(gè)顯示妹子圖的 activity。可以看到通過(guò) retrofit 獲取數(shù)據(jù),并顯示數(shù)據(jù)。

MVP

上文已經(jīng)提到了 MVC 的缺陷,接著便演化出來(lái)了 MVP 。

mvp設(shè)計(jì)模式.png
  • Model : javaBean 業(yè)務(wù)邏輯相關(guān)
  • View : activity fragemnt
  • presenter : 負(fù)責(zé)完成View于Model間的交互

從圖中可以看出, View 層和 Model 層已經(jīng)完全的解耦了,以前的 Controller 換成了 Presenter 作為它們之間的橋梁,用于操作 View 層發(fā)出的事件傳遞到 Presenter 中,Presenter 層再去操作 Model 層,獲取數(shù)據(jù)并返回給 View 層。

以下就將上文的代碼利用 MVP 實(shí)現(xiàn)。

  1. Model

    在以上代碼中,對(duì)數(shù)據(jù)進(jìn)行的操作有,最開(kāi)始的初始化數(shù)據(jù),刷新數(shù)據(jù),加載更多數(shù)據(jù)。對(duì)此創(chuàng)建一個(gè) HomeModel

     public interface HomeModel {
         void initializeDatas();
         void refreshDatas();
         void loadMoreDatas();
     }
    

    實(shí)現(xiàn)之:

     public class HomeModelmp implements HomeModel {
         public static final int REFRESH_DATA = 1;
         public static final int LOADMORE_DATA = 2;
         private HomeOnListener mListener;
         private int page;
     
         public HomeModelmp(HomeOnListener l) {
             this.mListener = l;
         }
     
         @Override
         public void initializeDatas() {
             getDatas(REFRESH_DATA);
         }
     
         @Override
         public void refreshDatas() {
             getDatas(REFRESH_DATA);
         }
     
         @Override
         public void loadMoreDatas() {
             getDatas(LOADMORE_DATA);
         }
     
         private void getDatas(final int action) {
     
             if (action == REFRESH_DATA) {
                 page = 1;
             } else {
                 page++;
             }
     
             ServiceManager.getInstance().mService.getWelfare(20, page)
                     .subscribeOn(Schedulers.io())
                     .observeOn(AndroidSchedulers.mainThread())
                     .subscribe(new Subscriber<BaseModel<List<Welfare>>>() {
                         @Override
                         public void onStart() {
                             mListener.onStart();
                         }
     
                         @Override
                         public void onCompleted() {
                             mListener.onCompleted();
                         }
     
                         @Override
                         public void onError(Throwable e) {
                             mListener.onError(e);
                         }
     
                         @Override
                         public void onNext(BaseModel<List<Welfare>> listBaseModel) {
                             mListener.onNext(action, listBaseModel);
                         }
                     });
         }
     
         public interface HomeOnListener {
             void onStart();
     
             void onCompleted();
     
             void onError(Throwable e);
     
             void onNext(int action, BaseModel<List<Welfare>> listBaseModel);
         }
     }
    
  2. View

    View 層就是 activity ,其中將只有純粹的UI操作。同樣創(chuàng)建一個(gè)接口:

     public interface HomeView {
         /**
          * 加載中
          */
         void onLoading();
     
         /**
          * 加載完成
          */
         void onLoadCompleted();
     
         /**
          * 加載布局
          */
         View inflateLayout(ViewGroup parent, boolean b);
     
         /**
          * 跳轉(zhuǎn)
          */
         void skipActivity(String url);
     
         /**
          * 獲取最后一個(gè)可見(jiàn)item位置
          */
         int findLastPosition();
     }
    

    將activity實(shí)現(xiàn)該接口:

     public class HomeActivity extends AppCompatActivity implements HomeView {
     
         private SwipeRecyclerView mSr;
         private HomePresenter mHomePresenter;
         private GridLayoutManager mLayoutManager;
     
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_home);
     
             if (mHomePresenter == null) {
                 mHomePresenter = new HomePresenter(this);
             }
             initView();
             initEvent();
             initData();
         }
     
         private void initData() {
             mHomePresenter.initialize();
         }
     
         private void initEvent() {
             mSr.setOnRefreshListener(mOnRefreshListener);
         }
     
         private void initView() {
             mSr = (SwipeRecyclerView) findViewById(R.id.activity_swipe_recycler);
             /**設(shè)置布局管理器*/
             mLayoutManager = new GridLayoutManager(this, 3);
             mSr.setLayoutManager(mLayoutManager);
             /**設(shè)置adapter*/
             mHomePresenter.setAdapter(mSr);
         }
     
         @Override
         public void onLoading() {
             mSr.mSwipeRefreshLayout.setRefreshing(true);
         }
     
         @Override
         public void onLoadCompleted() {
             mSr.mSwipeRefreshLayout.setRefreshing(false);
         }
     
         @Override
         public View inflateLayout(ViewGroup parent, boolean b) {
             return LayoutInflater.from(HomeActivity.this).inflate(R.layout.activity_recycler_item, parent, false);
         }
     
         @Override
         public void skipActivity(String url) {
             Intent intent = new Intent(HomeActivity.this, BitmapActivity.class);
             intent.putExtra("url", url);
             startActivity(intent);
         }
     
         @Override
         public int findLastPosition() {
             return mLayoutManager.findLastVisibleItemPosition();
         }
     
         private SwipeRecyclerView.OnRefreshListener mOnRefreshListener = new SwipeRecyclerView.OnRefreshListener() {
             @Override
             public void onRefresh() {
                 mHomePresenter.onRefreshData();
             }
         };
    
  3. Presenter

    Presenter作為 View 和 Model 之間交互的橋梁,也就是在合適的地方調(diào)用各自的方法。

     public class HomePresenter implements HomeModelmp.HomeOnListener {
     
         private HomeView mHomeView;
         private List<Welfare> mDatas = new ArrayList<>();
         private HomeActivity view;
         private HomeModel mHomeModel;
         private HomeAdapter mAdapter;
      
         public HomePresenter(HomeView homeView) {
             this.mHomeView = homeView;
             this.mHomeModel = new HomeModelmp(this);
         }
    
         public void initialize(){
             mDatas.clear();
             mHomeModel.initializeDatas();
         }
     
         public void onRefreshData() {
             mDatas.clear();
             mHomeModel.refreshDatas();
         }
     
         @Override
         public void onStart() {
             mHomeView.onLoading();
         }
     
         @Override
         public void onCompleted() {
             mHomeView.onLoadCompleted();
         }
     
         @Override
         public void onError(Throwable e) {
     
         }
     
         @Override
         public void onNext(int action, BaseModel<List<Welfare>> listBaseModel) {
             if (action == HomeModelmp.REFRESH_DATA) {
                 mDatas.clear();
             }
             mDatas.addAll(listBaseModel.results);
             mAdapter.notifyDataSetChanged();
         }
     
         public void setAdapter(SwipeRecyclerView sr) {
             mAdapter = new HomeAdapter();
             sr.setAdapter(mAdapter);
         }
     
         public class HomeAdapter extends SwipeRecyclerView.Adapter<HomeAdapter.MyViewHolder> {
     
             @Override
             public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                 MyViewHolder vh = new MyViewHolder(mHomeView.inflateLayout(parent, false));
                 return vh;
             }
     
             @Override
             public void onBindViewHolder(MyViewHolder holder, int position) {
                 final String url = mDatas.get(position).url;
     
                 Glide.with(holder.mImageView.getContext())
                         .load(url)
                         .centerCrop()
                         .placeholder(R.color.app_primary_dark)
                         .crossFade()
                         .into(holder.mImageView);
     
                 holder.mImageView.setOnClickListener(new View.OnClickListener() {
                     @Override
                     public void onClick(View v) {
                         mHomeView.skipActivity(url);
                     }
                 });
             }
     
             @Override
             public int getItemCount() {
                 if (mDatas == null)
                     return 0;
                 return mDatas.size();
             }
     
             @Override
             protected void loadMoreDatas(RecyclerView recyclerView, int dx, int dy) {
                 int position = mHomeView.findLastPosition();
                 int itemCount = getItemCount();
                 if (position + 1 == itemCount) {
                     mHomeModel.loadMoreDatas();
                 }
             }
     
             public class MyViewHolder extends RecyclerView.ViewHolder {
     
                 public ImageView mImageView;
     
                 public MyViewHolder(View itemView) {
                     super(itemView);
                     mImageView = (ImageView) itemView.findViewById(R.id.item_imageview);
                 }
             }
     
         }
     }
    

Ok,大功告成。這里僅是貼出了關(guān)鍵代碼,github地址:Gnak.iohttps://github.com/cgzysan/Gank.io</br>
參考資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,514評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,373評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,975評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,743評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,199評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,414評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,951評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,780評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,218評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,649評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,889評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,673評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容