安卓中MVC模式的深度思索和實(shí)踐(二)-頁(yè)面模塊化

這是一個(gè)有關(guān)安卓MVC框架模式的短系列,目的是思索和分析安卓中MVC模式更為真實(shí)的一面。

系列:

在上一篇中,主要從一個(gè)比較傳統(tǒng)但又精致的角度重新審視了一下安卓中的MVC模式。首先回顧一下上篇中最后有關(guān)MVC的觀點(diǎn),核心是分層,重點(diǎn)是職責(zé)如何單一和清晰化:

  • 視圖V,具備展示職責(zé),職責(zé)的劃分是通過(guò)與控制者的改變無(wú)關(guān)這條原則來(lái)進(jìn)行的;比如在安卓中,以LinearLayout為例,不管使用者怎么改變,它要么縱向布局,要么橫向布局,其本身就是這樣,而與使用的場(chǎng)景無(wú)關(guān)。整體來(lái)說(shuō),V層,就是要好好的擔(dān)起展示的職責(zé),不要把屬于展示的職責(zé),交給其它層去做。
  • 數(shù)據(jù)M,具備數(shù)據(jù)存取和操作職責(zé),職責(zé)的劃分是通過(guò)是否要與V發(fā)生聯(lián)系來(lái)進(jìn)行的。比如對(duì)于C來(lái)說(shuō),M應(yīng)該就是一個(gè)最終且最有目的性的需求品,C不該關(guān)心這個(gè)目標(biāo)數(shù)據(jù)怎么來(lái)的。這使得一般的校驗(yàn)和緩存等操作,應(yīng)該存在于M層。
  • 控制器C,或者叫做調(diào)度器,主要用來(lái)委托操作和調(diào)度,它不應(yīng)該著眼于業(yè)務(wù)邏輯或者視圖邏輯;而要將業(yè)務(wù)邏輯和視圖邏輯,都還給對(duì)應(yīng)的V層和M層。視圖業(yè)務(wù)邏輯,可視情況委托給業(yè)務(wù)工具類(lèi)(Helper)。對(duì)于調(diào)度職責(zé)來(lái)說(shuō),要構(gòu)建一個(gè)調(diào)度體系,上級(jí)調(diào)度管理下層調(diào)度,下層調(diào)度管理下下層調(diào)度,整體就是采用分而治之的思想,實(shí)現(xiàn)頁(yè)面級(jí)別的模塊化。

理論難免枯燥,本篇就以一個(gè)完整的簡(jiǎn)單demo來(lái)聊聊頁(yè)面的模塊化,本文主要說(shuō)明怎么分模塊的問(wèn)題,很多細(xì)節(jié)問(wèn)題沒(méi)有指出,這些細(xì)節(jié)問(wèn)題很多并不是demo中方案帶來(lái)的,而是項(xiàng)目實(shí)踐中都會(huì)遇到的。
demo的倉(cāng)庫(kù)地址

下面是demo項(xiàng)目的大體介紹。

  • 首先是demo的頁(yè)面原型圖,見(jiàn)附圖所示。


    頁(yè)面粗略原型圖
  • 需求分析:這個(gè)頁(yè)面是個(gè)整體可滾動(dòng)的視圖,單從原型圖來(lái)看在實(shí)際項(xiàng)目中應(yīng)該至少有一個(gè)網(wǎng)絡(luò)請(qǐng)求接口,甚至4個(gè);頂部UI需要填充多個(gè)數(shù)據(jù);WebView應(yīng)該有其對(duì)應(yīng)的配置甚至js交互;縱向list可能還能上拉加載等。滑動(dòng)沖突的問(wèn)題不作考慮,因?yàn)闊o(wú)論什么布局都要考慮。

  • 方案一,ScrollView嵌套LinearLayout,內(nèi)部縱向嵌套4種布局,分別對(duì)應(yīng)原型圖。這種方案應(yīng)該是80%以上的童鞋都會(huì)采用的方案;但是本文并不推薦采用這種方式。這里先列三條原因:1. 除了List里面的UI,所有的UI都將會(huì)出現(xiàn)在ScrollView所在的Controller中,少說(shuō)也要超過(guò)10個(gè)ui要find和設(shè)置數(shù)據(jù)吧,容易造成C的臃腫;2. 這種頁(yè)面肯定有網(wǎng)絡(luò)請(qǐng)求,假設(shè)以比較多的一種方式,4個(gè)接口來(lái)說(shuō),所有的數(shù)據(jù)請(qǐng)求以及給View設(shè)置都出現(xiàn)在C中,也會(huì)臃腫;3. 增加和刪除布局,會(huì)比較麻煩,因?yàn)镃中代碼臃腫耦合較多。

  • 方案二,ScrollView嵌套LinearLayout,內(nèi)部縱向嵌套4種布局,在C中分別替換成Fragment。這種方案相對(duì)方案一來(lái)說(shuō),有了一些進(jìn)步,至少一些View的find和數(shù)據(jù)請(qǐng)求被分散了。但本文仍不建議這樣,原因有三:1. Fragment的生命周期真的有點(diǎn)難以把控,尤其是當(dāng)上述布局出現(xiàn)在二級(jí)嵌套的Fragment中時(shí);2. Fragment的替換本身有一定的復(fù)雜度;3. 擴(kuò)展性有不足,若橫向List和縱向List交替實(shí)現(xiàn)多次,這種替換方式顯得被動(dòng)。

  • 方案三,當(dāng)然就是本文推薦的方式:RecyclerView作為大C中的唯一的UI存在,那四種布局,分別對(duì)應(yīng)四種item,即對(duì)應(yīng)四種holder。這么做的優(yōu)勢(shì)有:1. 大C幾乎只處理一個(gè)View的find和設(shè)置,網(wǎng)絡(luò)請(qǐng)求也只處理部分接口,C更多的作用是調(diào)度;2.RecyclerView的adapter負(fù)責(zé)分發(fā)布局,職責(zé)比較清晰;3. 對(duì)應(yīng)的幾個(gè)Holder都相當(dāng)于小C,分別處理View的find和set,也能處理自身的網(wǎng)絡(luò)請(qǐng)求;4. 布局增刪,復(fù)用都很容易。

  • 代碼實(shí)現(xiàn),還是上代碼比較容易理解,本demo省略了網(wǎng)絡(luò)部分。

  1. 大C的主要代碼
public class MainActivity extends AppCompatActivity {

    private RecyclerView mGlobalList;
    private GlobalAdapter mGlobalAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mGlobalList = (RecyclerView) findViewById(R.id.list_main_acty);
        mGlobalList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        mGlobalAdapter = new GlobalAdapter(this, Provider.create());
        mGlobalList.setAdapter(mGlobalAdapter);
    }
}
  1. adapter的主要代碼
public class GlobalAdapter extends RecyclerView.Adapter {

    private Activity mActivity;
    private Context mContext;
    private List<BaseBean> mDataList;

    public static final int TOP = 900;
    public static final int WEB = 901;
    public static final int GALLERY = 902;
    public static final int SHOW = 903;

    public GlobalAdapter(Activity aActivity, List<BaseBean> aDataList) {
        mActivity = aActivity;
        mContext = aActivity.getApplicationContext();
        mDataList = aDataList;
        if (mDataList == null) {
            mDataList = new ArrayList<>();
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case TOP:
                return new TopViewHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_top, parent, false));
            case WEB:
                return new WebHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_web, parent, false));
            case GALLERY:
                return new GalleryHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_gallery, parent, false));
            case SHOW:
                return new ShowHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_show, parent, false));
            default:
                break;
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ((BaseViewHolder) holder).bindData(mActivity, mDataList, position);

    }

    @Override
    public int getItemCount() {
        return mDataList.size();
    }

    @Override
    public int getItemViewType(int position) {
        return mDataList.get(position).getItemType();
    }
}
  1. 重點(diǎn)來(lái)了,數(shù)據(jù)和布局的分發(fā)是關(guān)鍵,畢竟這四種布局差別很大,數(shù)據(jù)也沒(méi)什么直接關(guān)系。但是本系列第一篇最后也提到,利用組合模式的思想,可實(shí)現(xiàn)數(shù)據(jù)的組合。還是上代碼好理解。

  2. 先定義一個(gè)基礎(chǔ)數(shù)據(jù)

public class BaseBean {
    private int itemType;

    public int getItemType() {
        return itemType;
    }

    public void setItemType(int aItemType) {
        itemType = aItemType;
    }
}
  1. 頂部ui的數(shù)據(jù)比較好解釋?zhuān)瑱?quán)且貼出;
public class TopBean extends BaseBean {
    private String name;
    private String imgHead;
    private int resImgHead;
    private int countNum;
    private int commentNum;
    private String description;
.......省略
}
  1. 重點(diǎn)是子列表型數(shù)據(jù)的處理,以橫向列表數(shù)據(jù)為例,這里看ContainerBean和GalleryBean;這里的兩個(gè)bean類(lèi)就使用了組合思想,讓ContainerBean可當(dāng)做一個(gè)元素,也可被用作列表元素,如此帶來(lái)的額外的好處是adapter也可被內(nèi)部list復(fù)用。
public class ContainerBean extends BaseBean {
    private List<BaseBean> dataList;

    public List<BaseBean> getDataList() {
        return dataList;
    }

    public void setDataList(List<BaseBean> aDataList) {
        dataList = aDataList;
    }
}
public class GalleryBean extends BaseBean {
    private String imgUrl;
    private int resId;
        ....省略
}
  1. Holder的處理也是關(guān)鍵,為了復(fù)用和減少很多代碼,一個(gè)父Holder是很重要的。里面有三個(gè)抽象方法,都有其作用。其中,數(shù)據(jù)接收或者網(wǎng)絡(luò)請(qǐng)求便可發(fā)生在bindData方法中。
public abstract class BaseViewHolder extends RecyclerView.ViewHolder implements IBindData, View.OnAttachStateChangeListener {
    protected Activity mActivity;
    public BaseViewHolder(View itemView) {
        super(itemView);
        itemView.addOnAttachStateChangeListener(this);
    }

    @Override
    public abstract void bindData(Activity aActivity, List<BaseBean> dataList, int position);

    @Override
    public void onViewAttachedToWindow(View v) {
        onAttached();
    }

    @Override
    public void onViewDetachedFromWindow(View v) {
        onDetached();
    }

    public abstract void onAttached();

    public abstract void onDetached();
}
  1. 最后,模擬一下數(shù)據(jù),搭建一下這個(gè)列表。
public class Provider {
    public static List<BaseBean> create() {
        List<BaseBean> baseBeanList = new ArrayList<>();
        //top
        BaseBean top = new TopBean();
        top.setItemType(GlobalAdapter.TOP);
        baseBeanList.add(top);
        //web
        WebBean web = new WebBean();
        web.setItemType(GlobalAdapter.WEB);
        web.setUrl("https://m.baidu.com/");
        baseBeanList.add(web);
        //ga
        List<BaseBean> innerGallery = new ArrayList<>();
        innerGallery.add(new GalleryBean());
        innerGallery.add(new GalleryBean());
           .......
        ContainerBean containerBean1 = new ContainerBean();
        containerBean1.setDataList(innerGallery);
        containerBean1.setItemType(GlobalAdapter.GALLERY);
        baseBeanList.add(containerBean1);
        //show
        List<BaseBean> shows = new ArrayList<>();
        shows.add(new ShowBean());
        shows.add(new ShowBean());
               .....
        ContainerBean containerBean2 = new ContainerBean();
        containerBean2.setDataList(shows);
        containerBean2.setItemType(GlobalAdapter.SHOW);
        baseBeanList.add(containerBean2);
        return baseBeanList;
    }
}

以上就是將頁(yè)面模塊的代碼實(shí)現(xiàn),整體還是比較簡(jiǎn)單的,通過(guò)這些設(shè)置,方案一中唯一的C將會(huì)變成5個(gè)C,頂級(jí)C是MainActivity,它和adapter共同來(lái)實(shí)現(xiàn)布局的調(diào)度和分發(fā);子布局分別對(duì)應(yīng)一個(gè)小C-holder,每個(gè)holder可實(shí)現(xiàn)其自己的邏輯。增加布局也很容易,只需增加一個(gè)新type數(shù)據(jù),增加一個(gè)holder即可;另外,布局復(fù)用也很方便,比如橫向List和縱向List交替復(fù)用,相當(dāng)于數(shù)據(jù)中出現(xiàn)了多個(gè)ContainnerBean,仍在List<BaseBean>和RecyclerView的適配中?;静挥脛?dòng)什么代碼。

寫(xiě)在本篇最后,本文主要目的是,通過(guò)一步步分析demo示例,揭示MVC中的C如何變得更小和清晰。demo粗糙,但想法不粗,希望能給讀者帶來(lái)啟發(fā)。在接下來(lái)的一篇中,將主要針對(duì)M層的優(yōu)化思想來(lái)場(chǎng)實(shí)戰(zhàn),以使得筆者的觀點(diǎn)更容易理解。

篇幅較長(zhǎng),碼字不易。

歡迎交流,共同進(jìn)步!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容