相信大家在Android编程中,应该会有一种体验:一个Activity要执行的功能太多了,导致代码有时甚至上千行,极其不方便阅读和修改,这里,面向Holder编程,就是解决这个问题的一个绝佳方案。
所谓面向Holder编程,其实很简单,就是把一个大的复杂的功能或者页面分成几个小的功能分别去实现,下面我们看一张图片来说明面向Holder编程思想:
这里,我们可以将该界面分成6个部分来完成:顶部导航栏、车城头条、车城服务、认证专区、热门推荐、购车攻略。
最后这个界面的布局如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
<com.che.chechengwang.support.view.ObservableScrollView android:id="@+id/sv_root" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffffff" android:overScrollMode="never" android:scrollbars="none">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical">
<!--车城头条-->
<FrameLayout android:id="@+id/fr_carHead" android:layout_width="match_parent" android:layout_height="wrap_content" />
<!--车城服务-->
<FrameLayout android:id="@+id/fr_carService" android:layout_width="match_parent" android:layout_height="wrap_content" />
<!--认证专区-->
<FrameLayout android:id="@+id/fr_authRegion" android:layout_width="match_parent" android:layout_height="wrap_content" />
<!--热门推荐-->
<FrameLayout android:id="@+id/fr_hotRecommend" android:layout_width="match_parent" android:layout_height="wrap_content" />
<!--购车攻略-->
<FrameLayout android:id="@+id/fr_buyGuide" android:layout_width="match_parent" android:layout_height="wrap_content" />
</LinearLayout>
</com.che.chechengwang.support.view.ObservableScrollView>
<!--顶部导航栏-->
<FrameLayout android:id="@+id/fr_searchbar" android:layout_width="match_parent" android:layout_height="wrap_content" />
</FrameLayout>
下面以”热门推荐”Holder来看看具体实现:
public class Holder_HotRecommend extends BaseHolder {
@Bind(R.id.more01)
View more01;
@Bind(R.id.more02)
ImageView more02;
@Bind(R.id.rl_more)
RelativeLayout rlMore;
@Bind(R.id.iv_hot01)
ImageView ivHot01;
@Bind(R.id.tv_hot01)
TextView tvHot01;
@Bind(R.id.iv_hot02)
ImageView ivHot02;
@Bind(R.id.tv_hot02)
TextView tvHot02;
@Bind(R.id.iv_hot04)
ImageView ivHot04;
@Bind(R.id.tv_hot04)
TextView tvHot04;
@Bind(R.id.iv_hot03)
ImageView ivHot03;
@Bind(R.id.tv_hot03)
TextView tvHot03;
@Bind(R.id.iv_hot05)
ImageView ivHot05;
@Bind(R.id.tv_hot05)
TextView tvHot05;
@Override
public View setContentView() {
return CarHelper.inflate(R.layout.holder_home_hotrecommend);
}
public void setRecommend(List<HomeResponse.HotListEntity> hotList) {
ImageView[] icons = new ImageView[]{ivHot01, ivHot02, ivHot03, ivHot04, ivHot05};
TextView[] titles = new TextView[]{tvHot01, tvHot02, tvHot03, tvHot04, tvHot05};
for (int i = 0; i < hotList.size(); i++) {
HomeResponse.HotListEntity item = hotList.get(i);
ImagePresenter.display(item.getImg(), icons[i]);
titles[i].setText(item.getName());
}
}
}
这里呢,我的Holder_HotRecommend是继承自一个基类BaseHolder,其实这个基类也没做啥,就是定义了加载布局的抽象方法,并注册了Butterknife而已,代码如下:
public abstract class BaseHolder {
private View contentView;
public BaseHolder() {
contentView = setContentView();
ButterKnife.bind(this, contentView);
init();
}
//把当前的view返回给父类
public View getContentView() {
return contentView;
}
//设置根视图
public abstract View setContentView();
//执行一些初始化的操作,非必须,所以空实现了,需要的话重写即可
public void init() {
}
}
这里呢,Activity(或Fragment)就像照片墙,我们只需要不断地往上贴照片,具体的照片拍成什么样,那个就不归activity管了。我这里呢,是用一个fragment来做照片墙的,那么如何贴上去呢,我们来看代码:
public class M1_HomeFragment extends CarBaseFragment {
@Bind(R.id.fr_carHead)
FrameLayout frCarHead;
@Bind(R.id.fr_carService)
FrameLayout frCarService;
@Bind(R.id.fr_authRegion)
FrameLayout frAuthRegion;
@Bind(R.id.fr_hotRecommend)
FrameLayout frHotRecommend;
@Bind(R.id.fr_buyGuide)
FrameLayout frBuyGuide;
@Bind(R.id.sv_root)
ObservableScrollView svRoot;
@Bind(R.id.fr_searchbar)
FrameLayout frSearchbar;
private Holder_SearchBar holderSearchBar;
private Holder_CarHead holderCarHead;
private Holder_CarService holderCarService;
private Holder_AuthRegion holderAuthRegion;
private Holder_HotRecommend holderHotRecommend;
private Holder_BuyGuide holderBuyGuide;
private View mainView;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mainView = inflater.inflate(R.layout.fragment_home_container, null);
ButterKnife.bind(this, mainView);
initView();
initData();
return mainView;
}
private void initView() {
holderSearchBar = new Holder_SearchBar();
holderCarHead = new Holder_CarHead();
holderCarService = new Holder_CarService();
holderAuthRegion = new Holder_AuthRegion();
holderHotRecommend = new Holder_HotRecommend();
holderBuyGuide = new Holder_BuyGuide();
frSearchbar.addView(holderSearchBar.getContentView());
frCarHead.addView(holderCarHead.getContentView());
frCarService.addView(holderCarService.getContentView());
frAuthRegion.addView(holderAuthRegion.getContentView());
frHotRecommend.addView(holderHotRecommend.getContentView());
frBuyGuide.addView(holderBuyGuide.getContentView());
holderSearchBar.setBannerHeight(DensityUtil.getScreenWidth() * 3 / 5);
svRoot.setScrollViewListener(holderSearchBar);
}
private void initData() {
HttpPresenter.getHomeInfo(getActivity().hashCode(), new OkHttpUtil.HttpCallBack<HomeResponse>() {
@Override
public void onSuccss(HomeResponse homeResponse) {
LogUtil.print(JsonUtil.getInstance().objToString(homeResponse));
holderCarHead.setBanner(homeResponse.getBannerList());
holderCarHead.setHeadInfo(homeResponse.getHeadInfo());
holderAuthRegion.setAuthList(homeResponse.getAuthList());
holderAuthRegion.setAcBanner(homeResponse.getActbannerlist());
holderHotRecommend.setRecommend(homeResponse.getHotList());
holderBuyGuide.setData(homeResponse.getBuyguideList());
}
@Override
public void onFailure(String error) {
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
holderCarHead.stopTask();
}
}
这里,其实就是用我们第一步的FrameLayout分别添加了每个Holder的contentView。
这里你也许会说,我的Holder是如何获取网络数据的呢?很简单,因为这里,我们的所有Holder都是同一个数据来源,所以我将获取数据的方法写在了照片墙上,当获取了数据之后,再给holder对象添加一个set方法,将需要的数据给set进去即可。如果我的Holder内中执行某个操作的时候,需要Activity对象,那么很简单,也将Activity对象给set进去。
这里看我的另一个Holder:
public class Holder_H5 extends BaseHolder {
@Bind(R.id.wv_content)
WebView wvContent;
String url;
Activity activity;
@Override
public View setContentView() {
return CarHelper.inflate(R.layout.holder_h5);
}
public void setUrl(String url) {
this.url = url;
initView();
}
public void setActivity(Activity activity) {
this.activity = activity;
}
public void initView() {
WebSettings setting = wvContent.getSettings();
setting.setJavaScriptEnabled(true);//支持js
setting.setLoadWithOverviewMode(true);//自适应手机屏幕
setting.setUseWideViewPort(true);
wvContent.setWebViewClient(new MyWebViewClient());
wvContent.loadUrl(url);
CarHelper.showDialog(activity);
}
private class MyWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
CarHelper.dismissDialog();
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return super.shouldOverrideUrlLoading(view, url);
}
}
}
其实这就是一个webview而已,那么在需要使用的地方,我们可以这样用:
public class M3_HelpSellFragment extends CarBaseFragment {
@Bind(R.id.fr_content)
FrameLayout frContent;
String url="http://m.che.com/m/helpSell.xhtml";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View mainView = inflater.inflate(R.layout.fragment_helpbuy, null);
ButterKnife.bind(this, mainView);
initView();
return mainView;
}
private void initView() {
Holder_H5 holderH5=new Holder_H5();
holderH5.setActivity(getActivity());
holderH5.setUrl(url);
frContent.addView(holderH5.getContentView());
}
@Override
public void onDestroyView() {
super.onDestroyView();
ButterKnife.unbind(this);
}
}
这里,我们先注入一个activity进去,再注入一个url进去,而在setUrl的方法中,我让它又调用了initView方法,这样一个holder_h5贴到墙上了,一个有加载进度条的网页activity就搞定了。
之前也看过别人写的一个一个BaseHolder,它是用的泛型,在构造时就需要你指明数据类型,感觉有点机械,它还帮你自定义了几个方法,但是实践发现,这个几个方法貌似没什么卵用,并不是总会用到,它的代码如下:
public abstract class BaseHolder<T> {
private View contentView;//界面
private T data;//数据的类型
//初始化的时候 先初始化空间,再把控件 都写到view里面(类似于listview的 view.setTag(holder))
public BaseHolder() {
contentView = initView();
contentView.setTag(this);
}
// 读取数据:注意这里的数据已经是 网络加载和json解析完 得到的 数据,
public void setData(T data) {
this.data = data;
refreshView(data);//显示界面
}
//把当前的view返回给父类
public View getView() {
return contentView;
}
/** * 初始化各种控件 * @return view */
protected abstract View initView();
/** * 控件添加各种数据 ,耗时炒作等 * @param data2 */
protected abstract void refreshView(T data);
}
详情见向前冲org的博客
我在设计自己的面向holder编程时,也参考了Top_Android的博客,他也是没有用泛型的。
我觉得用泛型的话、或者自定义方法的话,未免有点限制了,如果不是必须用的话,最好还是不用,哈哈,这就是我的编程逻辑。当然,你要是喜欢有泛型和自定义方法,也同样可以,这并不是重点,不是么?
相信经过以上的示例,你对于面向Holder编程应该已经了解了把,现在看一下我的结构:
怎么样?还算是比较清楚吧,以后再也不怕改了这一块,不小心让另一块也莫名其妙的变化了。
如果你有其它拆分和解耦的妙招,不妨也来说说哦!
PS:其实本来想将里面的set注入换成用Dragger2框架来实现的,不过看了Dragger2 的教程看了半天,还是不明白怎么用,汗!