这是一个有关安卓MVC框架模式的短系列,目的是思索和分析安卓中MVC模式更为真实的一面。
系列:
-安卓中MVC模式的深度思索和实践(一)
-安卓中MVC模式的深度思索和实践(三)
转载请标明出处: http://blog.csdn.net/cysion1989/article/details/71486875
在上一篇中,主要从一个比较传统但又精致的角度重新审视了一下安卓中的MVC模式。首先回顾一下上篇中最后有关MVC的观点,核心是分层,重点是职责如何单一和清晰化:
- 视图V,具备展示职责,职责的划分是通过与控制者的改变无关这条原则来进行的;比如在安卓中,以LinearLayout为例,不管使用者怎么改变,它要么纵向布局,要么横向布局,其本身就是这样,而与使用的场景无关。整体来说,V层,就是要好好的担起展示的职责,不要把属于展示的职责,交给其它层去做。
- 数据M,具备数据存取和操作职责,职责的划分是通过是否要与V发生联系来进行的。比如对于C来说,M应该就是一个最终且最有目的性的需求品,C不该关心这个目标数据怎么来的。这使得一般的校验和缓存等操作,应该存在于M层。
- 控制器C,或者叫做调度器,主要用来委托操作和调度,它不应该着眼于业务逻辑或者视图逻辑;而要将业务逻辑和视图逻辑,都还给对应的V层和M层。而视图和业务耦合的视图业务逻辑,可视情况委托给业务工具类(Helper)。对于调度职责来说,要构建一个调度体系,上级调度管理下层调度,下层调度管理下下层调度,整体就是采用分而治之的思想,实现页面级别的模块化。
本文主要来分析C的优化方式,理论难免枯燥,所以本篇就以一个完整的简单demo来聊聊页面的模块化,本文主要说明怎么分模块的问题,很多细节问题没有指出,这些细节问题很多并不是demo中方案带来的,而是项目实践中都会遇到的。
下面是demo项目的大体介绍。
首先是demo的页面原型图,见附图所示。
需求分析:这个页面是个整体可滚动的视图,单从原型图来看在实际项目中应该至少有一个网络请求接口,甚至4个;顶部UI需要填充多个数据;WebView应该有其对应的配置甚至js交互;纵向list可能还能上拉加载等。滑动冲突的问题不作考虑,因为无论什么布局都要考虑。
方案一,ScrollView嵌套LinearLayout,内部纵向嵌套4种布局,分别对应原型图。这种方案应该是80%以上的童鞋都会采用的方案;但是本文并不推荐采用这种方式。这里先列三条原因:1. 除了List里面的UI,所有的UI都将会出现在ScrollView所在的Controller中,少说也要超过10个ui要find和设置数据吧,容易造成C的臃肿;2. 这种页面肯定有网络请求,假设以比较多的一种方式,4个接口来说,所有的数据请求以及给View设置都出现在C中,也会臃肿;3. 增加和删除布局,会比较麻烦,因为C中代码臃肿耦合较多。
方案二,ScrollView嵌套LinearLayout,内部纵向嵌套4种布局,在C中分别替换成Fragment。这种方案相对方案一来说,有了一些进步,至少一些View的find和数据请求被分散了。但本文仍不建议这样,原因有三:1. Fragment的生命周期真的有点难以把控,尤其是当上述布局出现在二级嵌套的Fragment中时;2. Fragment的替换本身有一定的复杂度;3. 扩展性有不足,若横向List和纵向List交替实现多次,这种替换方式显得被动。
方案三,当然就是本文推荐的方式:RecyclerView作为大C中的唯一的UI存在,那四种布局,分别对应四种item,即对应四种holder。这么做的优势有:1. 大C几乎只处理一个View的find和设置,网络请求也只处理部分接口,C更多的作用是调度;2.RecyclerView的adapter负责分发布局,职责比较清晰;3. 对应的几个Holder都相当于小C,分别处理View的find和set,也能处理自身的网络请求;4. 布局增删,复用都很容易–by CysionLiu。
代码实现,还是上代码比较容易理解,本demo省略了网络部分。
大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);
}
}
- 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();
}
}
重点来了,数据和布局的分发是关键,毕竟这四种布局差别很大,数据也没什么直接关系。但是本系列第一篇最后也提到,利用组合模式的思想,可实现数据的组合。还是上代码好理解。
先定义一个基础数据
public class BaseBean {
private int itemType;
public int getItemType() {
return itemType;
}
public void setItemType(int aItemType) {
itemType = aItemType;
}
}
- 顶部ui的数据比较好解释,权且贴出;
public class TopBean extends BaseBean {
private String name;
private String imgHead;
private int resImgHead;
private int countNum;
private int commentNum;
private String description;
.......省略
}
- 重点是子列表型数据的处理,以横向列表数据为例,这里看ContainerBean和GalleryBean;这里的两个bean类就使用了组合思想,让ContainerBean可当做一个元素,也可被用作列表元素,如此带来的额外的好处是adapter也可被内部list复用–by CysionLiu。
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;
....省略
}
- Holder的处理也是关键,为了复用和减少很多代码,一个父Holder是很重要的。里面有三个抽象方法,都有其作用。其中,数据接收或者网络请求便可发生在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();
}
- 最后,模拟一下数据,搭建一下这个列表。
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;
}
}
以上就是将页面模块的代码实现,整体还是比较简单的,通过这些设置,方案一中唯一的C将会变成5个C,顶级C是MainActivity,它和adapter共同来实现布局的调度和分发;子布局分别对应一个小C-holder,每个holder可实现其自己的逻辑。增加布局也很容易,只需增加一个新type数据,增加一个holder即可;另外,布局复用也很方便,比如横向List和纵向List交替复用,相当于数据中出现了多个ContainnerBean,仍在List和RecyclerView的适配中。基本不用动什么代码。
写在本篇最后,本文主要目的是,通过一步步分析demo示例,揭示MVC中的C如何变得更小和清晰。demo粗糙,但想法不粗,希望能给读者带来启发。在接下来的一篇中,将主要针对M层的优化思想来场实战,以使得笔者的观点更容易理解。
篇幅较长,码字不易。
欢迎交流,共同进步!