版權宣告:本文為LooperJing原創文章,未經博主允許不得轉載!
在前幾篇,我總結了MVP,MVVM,對MVP使用泛型,以避免類爆炸,這些方案的實施在一定的程度的,使得View和業務邏輯成功隔離開來,但是對於一個複雜的介面,,一個layout.xml即使使用了
###一、複雜檢視分析
首先來看兩個圖,這是小米遊戲的詳情頁,將圖1上滑得到圖2
看上去,這個頁面很複雜了,分析一下UI結構,不全部展開,大概分成五個部分,最上方的三個TAB(介紹,評論,周邊)、遊戲滑動宣傳圖Banner、遊戲活動、遊戲推薦位、遊戲安裝。
試想,如果這個所有的子業務邏輯都寫在頁面載體中,從資料獲取,檢視設定、錯誤處理,互動跳轉,那麼這個Activity體量是很龐大的,維護相當困難。若你使用合理的架構將業務邏輯與檢視控制解耦,Activity體量確實明顯降低,但是體量還是很大,在JAVA的編碼規範中,每個類不能長於1000行,一個方法的長度儘量控制在50行,1000很容易就超過了。所以對於複雜的問題,"Divide and Conquer"(分而治之)的思想屢試不爽,按照業務功能級進行拆分,就得到複數個的檢視切片,於是在檢視層和業務邏輯層之間產生了以子業務為粒度的對映,業務檢視模組封裝了這類對映,每個模組封裝了特定子業務的檢視切片配置和業務邏輯。根據這種思想我將這個複雜的檢視分成4個部分,如下圖。
###二、實現
我們使用Holder來管理檢視,對於每一個檢視,基本功能具有獲取資料、重新整理資料、設定資料、findViewById、返回整個檢視給外部使用等,所以我寫出以下的基類。
public abstract class ViewBaseHolder<T> {
private T mBaseData;
private Context mContext;
private View mRootView;
public ViewBaseHolder(Context pContext) {
this(pContext, null, 0);
}
public ViewBaseHolder(Context pContext, ViewGroup pViewParent) {
this(pContext, pViewParent, 0);
}
public ViewBaseHolder(Context pContext, ViewGroup pViewParent, int pResId) {
this(pContext, pViewParent, pResId == 0 ? null : LayoutInflater.from(pContext).inflate(pResId, pViewParent, false));
}
public ViewBaseHolder(Context pContext, ViewGroup pViewParent, View pRootView) {
mContext = pContext;
mRootView = initView(pViewParent, pRootView);
initEvent(mRootView);
}
/**
* 獲取資料
*
* @return
*/
public T getData() {
return mBaseData;
}
/**
* 設定資料
* @param pData
*/
public void setDateAndRefreshView(T pData) {
mBaseData = pData;
refreshView(pData);
}
/**
* 用來通知重新整理資料
*/
public void notifyDataSetChange() {
refreshView(getData());
}
/**
* 用來重新整理資料
*/
public abstract void refreshView(T pData);
/**
*findViewById
*/
public abstract View initView(ViewGroup pViewParent, View pRootView);
/**
返回整個View給外部
*/
public View getRootView() {
return mRootView;
}
public void initEvent(View pBaseRootView) {}
public Context getContext() {
return mContext;
}
public Activity getActivity() {
if (mContext instanceof Activity) {
return (Activity) mContext;
}
return null;
}
}複製程式碼
對與遊戲推薦這個檢視切片,用DetailRecommendHoler 來管理,需要實現上面的ViewBaseHolder,考慮到互動跳轉的時候,需要伺服器得到某些資訊,把Activity中的Presenter傳進來了。DetailRecommendHoler 需要設定檢視資訊,給外部返回檢視根View,重新整理檢視,設定監聽事件等等。
public class DetailRecommendHoler extends ViewBaseHolder<GameEntry> {
private GameDetailPresenter mPresenter;
private TextView mGameName;
private ImageView mGameIcno;
public DetailRecommendHoler(Context pContext, ViewGroup pViewParent, GameDetailPresenter pPresenter) {
super(pContext, pViewParent);
mPresenter = pPresenter;
}
@Override
public void initEvent(View pBaseRootView) {
super.initEvent(pBaseRootView);
mGameIcno.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//mPresenter.toDownLoadGameActivity(getContext());
}
});
}
public DetailRecommendHoler(Context pContext, ViewGroup pViewParent, int pResId, GameDetailPresenter pPresenter) {
super(pContext, pViewParent, pResId);
mPresenter = pPresenter;
}
@Override
public void refreshView(GameEntry pData) {
mGameIcno.setImageResource(pData.url);
mGameName.setText(pData.name);
}
@Override
public View initView(ViewGroup pViewParent, View pRootView) {
View rootView;
if (pRootView == null) {
rootView = LayoutInflater.from(getContext()).inflate(R.layout.game_recommend_holder, null);
} else {
rootView = pRootView;
}
mGameIcno = (ImageView) pRootView.findViewById(R.id.game_inco);
mGameName = (TextView) pRootView.findViewById(R.id.game_name);
return rootView;
}
public GameDetailPresenter getPresenter() {
return mPresenter;
}
}複製程式碼
同理對於遊戲詳情中1檢視切片可以用DetailBannerHoler來管理,對於遊戲詳情中2檢視切片可以用DetailActivityHoler來管理,對於遊戲詳情中4檢視切片可以用DetailDownLoadHoler來管理。通過這樣的分而治之,完全可以避免一個頁面太重的影響,一個檢視切片程式碼量少,維護起來很方便,比如對於上面的的3切片,如果有其他模組需要使用,直接使用即可。相應的,經過“分而治之”之後,layout也分成了幾個部分,如下。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<FrameLayout
android:id="@+id/game_banner_container"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="60dp"
/>
<FrameLayout
android:id="@+id/game_activity_container"
android:layout_width="match_parent"
android:layout_height="300dp"
android:layout_below="@id/game_banner_container"
/>
<FrameLayout
android:id="@+id/game_recomend_container"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_below="@+id/game_activity_container"
/>
<FrameLayout
android:id="@+id/game_download_container"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_below="@+id/game_recomend_container"
/>
</RelativeLayout>複製程式碼
OK,現在看如何使用它,見證一下效果。下面有太不明白的,需要了解一下,MVP(戳我)[www.jianshu.com/p/3a17382d4…]
public class GameDetailActivity extends BaseActivity<GameDetailPresenter, GameDtailModel> implements GameContract.GameDetailView {
private FrameLayout mGameRecommendFly;
private FrameLayout mGameActivityFly;
private FrameLayout mGameDownLoadFly;
private FrameLayout mGameBannerFly;
private DetailRecommendHoler mDetailRecommendHoler;
private DetailBannerHoler mDetailBannerHoler;
private ProgressBar mLoadingBar;
private ListView mListView;
@Override
public int getLayoutResId() {
return R.layout.activity_main;
}
@Override
public void initView() {
mPresenter.requestGameEntry(1, 1);
mGameRecommendFly = (FrameLayout) findViewById(R.id.game_recomend_container);
mDetailRecommendHoler = new DetailRecommendHoler(this, mGameRecommendFly, mPresenter);
mGameRecommendFly.addView(mDetailRecommendHoler.getRootView());
mGameBannerFly = (FrameLayout) findViewById(R.id.game_banner_container);
mDetailRecommendHoler = new DetailBannerHoler(this, mGameBannerFly, mPresenter);
mGameBannerFly.addView(mDetailBannerHoler.getRootView());
...
}
@Override
public void showLoading() {
mLoadingBar.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
mLoadingBar.setVisibility(View.INVISIBLE);
}
@Override
public void showError() {
TextView errorView = new TextView(this);
errorView.setTextSize(20);
errorView.setText("請求失敗了");
mListView.setEmptyView(errorView);
}
@Override
public void setGameEntry(GameEntry pGameEntry) {
mDetailRecommendHoler.refreshView(pGameEntry.listone);
mDetailRecommendHoler.refreshView(pGameEntry.listtwo);
.......
}
}複製程式碼
這種寫法,Activity/Fragment本身不再承載任何檢視業務邏輯,僅僅需要維護內部寄生的模組Activity/Fragment的職責退化為模組容器和資料媒介,資料媒介的作用體現在,Activity/Fragment在某些情況下會扮演頁面資料的入口和分發者,Data到達Activity後,Activity要將Data再次分發給內部那些真正需要資料的模組。對於檢視業務模組顯得簡單直白,結構良好,因為這一步劃分了邊界,使得頁面結構的明晰化。我使用這種思想的難點是檢視切片的粒度劃分,太細需要寫很多程式碼,太少達不到效果,這個需要根據需求來把握,思想是活的,模式之間相互變通,才能寫出良好的系統結構,減少開發維護成本。
推薦閱讀:
Android架構設計---MVP模式第(一)篇之基本認實
Android架構設計---MVP模式第(二)篇,如何減少類爆炸
Android架構設計---關於MVVM模式的探討
Please accept mybest wishes for your happiness and success !