Android MVP进阶:“修行在个人”

作者: 星际之痕 | 来源:发表于2016-07-31 19:43 被阅读1376次

    距离上一篇Android MVP从懵逼到入门:登陆业务实践已经有一段时间了,这段时间忙着公司的项目,都没找时间写写文章,今天就把这段时间整理的MVP知识再总结一下,这篇文章主要介绍我是如何使用MVP模式来实现主页多个Tab切换的场景的。

    说是复杂,其实只是多了一些Fragment而已,在上一篇文章中,我们说Activity的主要作用是创建view和presenter,并把view实例传入到presenter中,那么,对于多个Tab切换的主页面(现在的大多数app都是这样的主页),Activity的作用就又多了一个,也是大家都知道的:管理Fragment。其实这没什么想不到的,在没说MVP的时候,大家也都是这么做的。

    这里我参考网上网友收集的知乎日报的相关api,使用mvp模式简单的实现了知乎日报主页的内容。先看一下效果:


    主页效果图主页效果图

    presenter

    先看看presenter怎么写,应该说跟上一篇的实现是一样的,那就得先写好契约类--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接口,可以知道,页面可以实现的功能有:

    • 1.设置页面标题--setTitle
    • 2.刷新页面内容--refresh

    看presenter接口,可以知道,model提供了那些数据访问接口:- 1.获取今日日报模块内容- 2.获取网络安全模块内容- 3.获取不许无聊模块内容- 4.获取体育日报模块内容契约类写好后,就可以实现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实现类的集合,比如这里有4个Fragment页面,每个页面展示的内容是不一样的,需要的接口也不一样,这些接口都需要在契约类里面定义,然后每个Fragment都去实现这个接口,完成对应接口中的内容。

    比如这4个Fragment都去实现MainContract.View,各自都实现setTitle和refresh内容,如何还有其他需要实现的接口,同样定义在契约类里面,谁关心这个接口,谁就去实现这个接口,view不必关心接口的调用,只需要实现内容就行了,这在MVP中体现的比较明显:面向接口编程。(我所接触的面向接口编程的另一种场景就是模块化编程,感觉有些类似。)

    既然有4个Fragment,那么他们肯定有共同之处,至少他们都是Fragment,何不提取基类呢?这相比大家都能想到--Java语言的三大特性即:封装、继承、多态

    来看看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

    因为这里没有考虑到数据的持久化,没有创建本地数据库,所以,model的实现其实就是一个service,因为是用reftrofit2来完成网络请求的,所以,model就是ZhiHuService.java:

    public interface ZhiHuService {
    
        //今日头条
        @GET("/api/4/news/latest")
        Observable<RootEntity> getLatestNews();
    
        //互联网安全
        @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,作用有三个:创建view(这里是views)、创建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实现类,所以,我没有在presenter的初始化时传入view实例,而是通过在presenter中的setView方法设置不同的view。

    跟retrofit2相关的内容这里不展开说,不知道的、想了解的可以自行搜索,这里是GitHub地址:retrofit,以及本文的项目源码:Login-MVP-Architecture ,希望对MVP感兴趣以及任何想要一起提高Android开发技能的小伙伴能star一下,以后会继续在这个项目上添加新的内容,包括一些有用的工具类、各种控件、效果实现、设计模式的使用等等。

    周末愉快(●'◡'●)

    相关文章

      网友评论

      • 灯不利多:没有给switch的case封装出方法来
        灯不利多:@星际之痕 哈哈,封装好的case更漂亮 :smile:
        星际之痕:@Jay1993 搜噶,这件事就交给你啦!:smile:
      • 3ee6e9ef0293:封装继承多态,抽象
        星际之痕:一般说三大特性,不过加上抽象也无可厚非:smile:这里主要想说的是:继承!

      本文标题:Android MVP进阶:“修行在个人”

      本文链接:https://www.haomeiwen.com/subject/pofxsttx.html