版權聲明:本文爲LooperJing原創文章,未經博主容許不得轉載!javascript
在前幾篇,我總結了MVP,MVVM,對MVP使用泛型,以免類爆炸,這些方案的實施在必定的程度的,使得View和業務邏輯成功隔離開來,可是對於一個複雜的界面,,一個layout.xml即便使用了
###1、複雜視圖分析
首先來看兩個圖,這是小米遊戲的詳情頁,將圖1上滑獲得圖2android
看上去,這個頁面很複雜了,分析一下UI結構,不所有展開,大概分紅五個部分,最上方的三個TAB(介紹,評論,周邊)、遊戲滑動宣傳圖Banner、遊戲活動、遊戲推薦位、遊戲安裝。
服務器
試想,若是這個全部的子業務邏輯都寫在頁面載體中,從數據獲取,視圖設置、錯誤處理,交互跳轉,那麼這個Activity體量是很龐大的,維護至關困難。若你使用合理的架構將業務邏輯與視圖控制解耦,Activity體量確實明顯下降,可是體量仍是很大,在JAVA的編碼規範中,每一個類不能長於1000行,一個方法的長度儘可能控制在50行,1000很容易就超過了。因此對於複雜的問題,"Divide and Conquer"(分而治之)的思想屢試不爽,按照業務功能級進行拆分,就獲得複數個的視圖切片,因而在視圖層和業務邏輯層之間產生了以子業務爲粒度的映射,業務視圖模塊封裝了這類映射,每一個模塊封裝了特定子業務的視圖切片配置和業務邏輯。根據這種思想我將這個複雜的視圖分紅4個部分,以下圖。架構
###2、實現
咱們使用Holder來管理視圖,對於每個視圖,基本功能具備獲取數據、刷新數據、設置數據、findViewById、返回整個視圖給外部使用等,因此我寫出如下的基類。app
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,刷新視圖,設置監聽事件等等。ide
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也分紅了幾個部分,以下。oop
<?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…]this
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 !