Android 架構設計 --- 關於 View 邊界劃分的思考

版權聲明:本文爲LooperJing原創文章,未經博主容許不得轉載!javascript

在前幾篇,我總結了MVP,MVVM,對MVP使用泛型,以免類爆炸,這些方案的實施在必定的程度的,使得View和業務邏輯成功隔離開來,可是對於一個複雜的界面,,一個layout.xml即便使用了 和自定義控件,上千行也是頗有可能的。因此這篇博客,主要記錄 業務視圖模塊怎麼編寫比較好,固然這不是教科書,只是分享我關於這方面的思考。 java

###1、複雜視圖分析
首先來看兩個圖,這是小米遊戲的詳情頁,將圖1上滑獲得圖2android

圖1

圖2

看上去,這個頁面很複雜了,分析一下UI結構,不所有展開,大概分紅五個部分,最上方的三個TAB(介紹,評論,周邊)、遊戲滑動宣傳圖Banner、遊戲活動、遊戲推薦位、遊戲安裝。
服務器

UI結構分析

試想,若是這個全部的子業務邏輯都寫在頁面載體中,從數據獲取,視圖設置、錯誤處理,交互跳轉,那麼這個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 !

相關文章
相關標籤/搜索