距離上一篇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)、設計模式的使用等等来氧。