Android MVP進階:“修行在個人”

距離上一篇Android MVP從懵逼到入門:登陸業(yè)務實踐已經有一段時間了外厂,這段時間忙著公司的項目冕象,都沒找時間寫寫文章,今天就把這段時間整理的MVP知識再總結一下汁蝶,這篇文章主要介紹我是如何使用MVP模式來實現(xiàn)主頁多個Tab切換的場景的渐扮。

說是復雜,其實只是多了一些Fragment而已掖棉,在上一篇文章中墓律,我們說Activity的主要作用是創(chuàng)建view和presenter,并把view實例傳入到presenter中幔亥,那么耻讽,對于多個Tab切換的主頁面(現(xiàn)在的大多數(shù)app都是這樣的主頁),Activity的作用就又多了一個帕棉,也是大家都知道的:管理Fragment针肥。其實這沒什么想不到的,在沒說MVP的時候香伴,大家也都是這么做的慰枕。

這里我參考網上網友收集的知乎日報的相關api,使用mvp模式簡單的實現(xiàn)了知乎日報主頁的內容即纲。先看一下效果:


主頁效果圖
主頁效果圖

presenter

先看看presenter怎么寫捺僻,應該說跟上一篇的實現(xiàn)是一樣的,那就得先寫好契約類--MainContact.java:

public interface MainContract { 
    interface Presenter extends BasePresenter { 
        RootEntity getLatestNews(); 
        RootEntity getSafety(); 
        RootEntity getInterest(); 
        RootEntity getSport(); 
    } 
    interface View extends BaseView<Presenter> { 
        void setTitle(); 
        void refresh(List<StoriesEntity> list); 
    }
}

寫好契約類崇裁,那么這個模塊有哪些主要功能基本都清楚了,看view接口束昵,可以知道拔稳,頁面可以實現(xiàn)的功能有:

  • 1.設置頁面標題--setTitle
  • 2.刷新頁面內容--refresh

看presenter接口,可以知道锹雏,model提供了那些數(shù)據(jù)訪問接口:- 1.獲取今日日報模塊內容- 2.獲取網絡安全模塊內容- 3.獲取不許無聊模塊內容- 4.獲取體育日報模塊內容契約類寫好后巴比,就可以實現(xiàn)presenter接口了,看看MainPresenter.java類:

public class MainPresenter implements MainContract.Presenter {

    private String baseUrl = "http://news-at.zhihu.com";

    private MainContract.View mMainView;
    private Context mContext;

    protected ZhiHuService service;

    public MainPresenter(Context context) {
        this.mContext = context;
    }

    public void setView(MainContract.View view) {
        this.mMainView = view;
        mMainView.setPresenter(this);
        mMainView.setTitle();
        service = getService();
    }

    public ZhiHuService getService() {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
        service = retrofit.create(ZhiHuService.class);
        return service;
    }

    @Override
    public RootEntity getLatestNews() {
        return loadData(service.getLatestNews());
    }

    @Override
    public RootEntity getSafety() {
        return loadData(service.getSafety());
    }

    @Override
    public RootEntity getInterest() {
        return loadData(service.getInterest());
    }

    @Override
    public RootEntity getSport() {
        return loadData(service.getSport());
    }

    @Override
    public void start() {

    }

    public RootEntity loadData(Observable<RootEntity> observable) {
        final RootEntity rootEntity = new RootEntity();
        observable.observeOn(AndroidSchedulers.mainThread())
                .subscribeOn(Schedulers.io())
                .map(new Func1<RootEntity, ArrayList<StoriesEntity>>() {
                    @Override
                    public ArrayList<StoriesEntity> call(RootEntity rootEntity) {
                        return rootEntity.getStories();
                    }
                })
                .subscribe(new Subscriber<ArrayList<StoriesEntity>>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(ArrayList<StoriesEntity> storiesEntities) {
                        rootEntity.setStories(storiesEntities);
                        mMainView.refresh(storiesEntities);
                    }
                });

        return rootEntity;
    }
}

view

view其實才是這篇文章的主題,契約類里面定義的一些接口其實是所有的view實現(xiàn)類的集合轻绞,比如這里有4個Fragment頁面采记,每個頁面展示的內容是不一樣的,需要的接口也不一樣政勃,這些接口都需要在契約類里面定義唧龄,然后每個Fragment都去實現(xiàn)這個接口,完成對應接口中的內容奸远。

比如這4個Fragment都去實現(xiàn)MainContract.View既棺,各自都實現(xiàn)setTitle和refresh內容,如何還有其他需要實現(xiàn)的接口懒叛,同樣定義在契約類里面丸冕,誰關心這個接口,誰就去實現(xiàn)這個接口薛窥,view不必關心接口的調用胖烛,只需要實現(xiàn)內容就行了,這在MVP中體現(xiàn)的比較明顯:面向接口編程诅迷。(我所接觸的面向接口編程的另一種場景就是模塊化編程佩番,感覺有些類似。)

既然有4個Fragment竟贯,那么他們肯定有共同之處答捕,至少他們都是Fragment,何不提取基類呢屑那?這相比大家都能想到--Java語言的三大特性即:封裝拱镐、繼承、多態(tài)持际。

來看看BaseFragment的內容:

public class BaseFragment extends Fragment implements MainContract.View {

    @BindView(R.id.lv_news)
    ListView mListView;

    protected MainContract.Presenter mPresenter;
    protected ActionBar mActionBar;
    private ZhiHuNewsAdapter mAdapter;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main_0, container, false);
        ButterKnife.bind(this, view);

        return view;
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        mAdapter = new ZhiHuNewsAdapter(getContext());
        mListView.setAdapter(mAdapter);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (isAdded()) {
            mActionBar = ((AppCompatActivity)getActivity()).getSupportActionBar();
            setTitle();
        }
    }

    @Override
    public void setPresenter(MainContract.Presenter presenter) {
        this.mPresenter = presenter;
    }

    @Override
    public void refresh(final List<StoriesEntity> list) {
        mAdapter.setNewsList(list);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(getActivity(), StoryDetailActivity.class);
                intent.putExtra(StoryDetailFragment.STORY_ID, list.get(position).getId());
                intent.putExtra(StoryDetailFragment.STORY_TITLE, list.get(position).getTitle());
                startActivity(intent);
            }
        });
    }

    @Override
    public void setTitle() {
        if (mActionBar != null) {
            mActionBar.setTitle(R.string.app_name);
        }
    }
}

model

因為這里沒有考慮到數(shù)據(jù)的持久化沃琅,沒有創(chuàng)建本地數(shù)據(jù)庫,所以蜘欲,model的實現(xiàn)其實就是一個service益眉,因為是用reftrofit2來完成網絡請求的,所以姥份,model就是ZhiHuService.java:

public interface ZhiHuService {

    //今日頭條
    @GET("/api/4/news/latest")
    Observable<RootEntity> getLatestNews();

    //互聯(lián)網安全
    @GET("/api/4/theme/10")
    Observable<RootEntity> getSafety();

    //不準無聊
    @GET("/api/4/theme/11")
    Observable<RootEntity> getInterest();

    //體育日報
    @GET("/api/4/theme/8")
    Observable<RootEntity> getSport();
}

avtivity

最后看看activity郭脂,作用有三個:創(chuàng)建view(這里是views)、創(chuàng)建presenter澈歉、管理fragemnt:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tab_item_main_0)
    TabItem tabItemMain0;

    @BindView(R.id.tab_item_main_1)
    TabItem tabItemMain1;

    @BindView(R.id.tab_item_main_2)
    TabItem tabItemMain2;

    @BindView(R.id.tab_item_main_3)
    TabItem tabItemMain3;

    @BindView(R.id.tab_item_main_4)
    TabItem tabItemMain4;

    @BindView(R.id.toolbar)
    Toolbar toolbar;

    @BindView(R.id.fab)
    FloatingActionButton floatingBar;

    private FragmentManager mFragmentManager;
    private TodayFragment mTodayFragment;
    private InterestFragment mInterestFragment;
    private SafetyFragment mSafetyFragment;
    private SportFragment mSportFragment;
    private OtherFragment mOtherFragment;

    private MainPresenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        mFragmentManager = getSupportFragmentManager();
        setSupportActionBar(toolbar);

        mPresenter = new MainPresenter(getApplicationContext());

        tabItemMain0.performClick();
    }

    public void showFragment(int tag) {
        if (mFragmentManager != null) {
            FragmentTransaction transaction = mFragmentManager.beginTransaction();
            hideFragments();
            switch (tag) {
                case TagStatic.TAG_FRAGMENT_TODAY:
                    mTodayFragment = (TodayFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_TODAY + "");
                    if (mTodayFragment == null) {
                        mTodayFragment = new TodayFragment();
                        transaction.add(R.id.fragment_content, mTodayFragment, tag + "");
                    } else {
                        transaction.show(mTodayFragment);
                    }
                    mPresenter.setView(mTodayFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_INTEREST:
                    mInterestFragment = (InterestFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_INTEREST + "");
                    if (mInterestFragment == null) {
                        mInterestFragment = new InterestFragment();
                        transaction.add(R.id.fragment_content, mInterestFragment, tag + "");
                    } else {
                        transaction.show(mInterestFragment);
                    }
                    mPresenter.setView(mInterestFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_SAFETY:
                    mSafetyFragment = (SafetyFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SAFETY + "");
                    if (mSafetyFragment == null) {
                        mSafetyFragment = new SafetyFragment();
                        transaction.add(R.id.fragment_content, mSafetyFragment, tag + "");
                    } else {
                        transaction.show(mSafetyFragment);
                    }
                    mPresenter.setView(mSafetyFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_SPORT:
                    mSportFragment = (SportFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SPORT + "");
                    if (mSportFragment == null) {
                        mSportFragment = new SportFragment();
                        transaction.add(R.id.fragment_content, mSportFragment, tag + "");
                    } else {
                        transaction.show(mSportFragment);
                    }
                    mPresenter.setView(mSportFragment);
                    break;

                case TagStatic.TAG_FRAGMENT_OTHER:
                    mOtherFragment = (OtherFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_OTHER + "");
                    if (mOtherFragment == null) {
                        mOtherFragment = new OtherFragment();
                        transaction.add(R.id.fragment_content, mOtherFragment, tag + "");
                    } else {
                        transaction.show(mOtherFragment);
                    }
                    mPresenter.setView(mOtherFragment);
                    break;
            }

            transaction.commitAllowingStateLoss();
        }
    }

    private void hideFragments() {
        FragmentTransaction transaction = mFragmentManager.beginTransaction();
        mTodayFragment = (TodayFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_TODAY + "");
        if (mTodayFragment != null) {
            transaction.hide(mTodayFragment);
        }

        mInterestFragment = (InterestFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_INTEREST + "");
        if (mInterestFragment != null) {
            transaction.hide(mInterestFragment);
        }

        mSafetyFragment = (SafetyFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SAFETY + "");
        if (mSafetyFragment != null) {
            transaction.hide(mSafetyFragment);
        }

        mSportFragment = (SportFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_SPORT + "");
        if (mSportFragment != null) {
            transaction.hide(mSportFragment);
        }

        mOtherFragment = (OtherFragment) mFragmentManager.findFragmentByTag(TagStatic.TAG_FRAGMENT_OTHER + "");
        if (mOtherFragment != null) {
            transaction.hide(mOtherFragment);
        }

        transaction.commitAllowingStateLoss();
    }

    @OnClick({R.id.tab_item_main_0, R.id.tab_item_main_1, R.id.tab_item_main_2,
            R.id.tab_item_main_3, R.id.tab_item_main_4, R.id.fab})
    public void onClick(View view) {
        clearChecked();
        switch (view.getId()) {
            case R.id.tab_item_main_0:
                tabItemMain0.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_TODAY);
                break;

            case R.id.tab_item_main_1:
                tabItemMain1.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_INTEREST);
                break;

            case R.id.tab_item_main_2:
                tabItemMain2.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_SAFETY);
                break;

            case R.id.tab_item_main_3:
                tabItemMain3.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_SPORT);
                break;

            case R.id.tab_item_main_4:
                tabItemMain4.setChecked(true);
                showFragment(TagStatic.TAG_FRAGMENT_OTHER);
                break;

            case R.id.fab:
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_SHORT)
                        .setAction("Action", null).show();
                break;
        }
    }

    private void clearChecked() {
        tabItemMain0.setChecked(false);
        tabItemMain1.setChecked(false);
        tabItemMain2.setChecked(false);
        tabItemMain3.setChecked(false);
        tabItemMain4.setChecked(false);
    }
}

avtivity里的內容還是比較少的展鸡,只是多了Fragment的顯示和隱藏,沒有變得復雜和職責不清埃难,對于管理和維護也很簡單莹弊。因為有多個view實現(xiàn)類涤久,所以,我沒有在presenter的初始化時傳入view實例忍弛,而是通過在presenter中的setView方法設置不同的view响迂。

跟retrofit2相關的內容這里不展開說,不知道的细疚、想了解的可以自行搜索蔗彤,這里是GitHub地址:retrofit,以及本文的項目源碼:Login-MVP-Architecture 惠昔,希望對MVP感興趣以及任何想要一起提高Android開發(fā)技能的小伙伴能star一下幕与,以后會繼續(xù)在這個項目上添加新的內容,包括一些有用的工具類镇防、各種控件啦鸣、效果實現(xiàn)、設計模式的使用等等来氧。

周末愉快(●'?'●)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末诫给,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啦扬,更是在濱河造成了極大的恐慌中狂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扑毡,死亡現(xiàn)場離奇詭異胃榕,居然都是意外死亡,警方通過查閱死者的電腦和手機瞄摊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門换帜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惯驼,你說我怎么就攤上這事蹲嚣。” “怎么了禾蚕?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵倍试,是天一觀的道長。 經常有香客問我躁愿,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上日矫,老公的妹妹穿的比我還像新娘彭沼。我一直安慰自己褐奴,他們只是感情好唯沮,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布溶褪。 她就那樣靜靜地躺著巍虫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上担忧,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死厂镇,一個胖子當著我的面吹牛捺信,可吹牛的內容都是我干的。 我是一名探鬼主播欠痴,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼梨水!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤萍倡,失蹤者是張志新(化名)和其女友劉穎身弊,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體列敲,經...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡凑术,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了所意。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淮逊。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扁眯,靈堂內的尸體忽然破棺而出壮莹,到底是詐尸還是另有隱情,我是刑警寧澤姻檀,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布命满,位于F島的核電站,受9級特大地震影響绣版,放射性物質發(fā)生泄漏胶台。R本人自食惡果不足惜歼疮,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望诈唬。 院中可真熱鬧韩脏,春花似錦、人聲如沸铸磅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阅仔。三九已至吹散,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間八酒,已是汗流浹背空民。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留羞迷,地道東北人界轩。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像衔瓮,于是被迫代替她去往敵國和親浊猾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內容