日常开发中很少会碰到ScrollView中嵌套listview或webview的情况,而且谷歌官方也不推荐这么做,但是也不是一定不会有这样的需求,毕竟定需求的不是我们程序员,而是产品经理。比如像下面这种需求:
可以看到,整个页面有一个共同的头部,下面有两个tab,左边tab下是个可以滚动的webview,右边是个listview。要求listview和webview在默认情况下不滚动,但外部整个页面可以滚动,当外层页面滚动到底部时,也就是两个tab的位置大概位于actionbar下方的时候,要求listview和webview自己能滑动而外层不动,当listview或webview下拉到顶部时,又让外层接管滑动,此时共同的头部可以拉下来。
碰到这种情况,一般的情况肯定是外层套个scrollview,两个tab里分别放个fragment,左右两个fragment分别放置listview。但是很不幸,这样做之后发现listview根本连显示都显示不了,更别提可以滑动了。读者一试便知。原因就在与scrollview和listview存在滑动事件的冲突,那么如何解决这个问题呢?网上有人给出了一个方法:手动计算listview的高度然后显示地设置他的高度。你只需要在listview.setAdapter()方法后调用如下代码:
public void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
Log.d(TAG, "params.height: "+params.height);
listView.setLayoutParams(params);
}
很不幸!这段代码只有在没有fragment的情况下才有效果,现在的情况是scrollview在宿主Activity中,而listview却在其中一个fragment中,按理说fragment也是放在Activity中那就相当于listview也在Activity中,但是实际情况就是显示不出来listview,具体什么原因我暂时也不清楚。
那既然在没有fragment的情况下才可以显示listview,那如果不用fragment怎么达到上述切换的效果呢?很简单!在Activity中切换的位置放置个空的容器FrameLayout,然后在切换tab的时候动态添加进想要的view,无论是webvie还是listview甚至是更复杂的view。其实在fragment出现之前,解耦Activity就采用的是这中方式。
我们采用mvc的思想抽象出一个controller基类,功能类似于一个简单的fragment,里面可以绑定view视图和model数据。
public abstract class BaseController
{
public View mRootView;
public Context mContext;
public BaseController(Context context){
this.mContext = context;
// 在构造中 就加载显示的view
mRootView = initView(context);
}
/**
* 初始化view的方法让子类去实现
* @return
*/
protected abstract View initView(Context context);
/**
* 加载数据的方法,子类可以实现,也可以不实现
*/
public void initData(){
}
/**
* 暴露出去的获得根view的方法
* @return
*/
public View getRootView()
{
return mRootView;
}
}
然后继承这个基类分别创建LeftController 和 RightController,我们先以RightController为例,来把显示listview显示出来。
public class RightController extends BaseController {
private static final String TAG = "RightController";
private ListView mListView;
private List<String> mDatas;
public RightController(Context context) {
super(context);
}
@Override
protected View initView(Context context) {
// TextView readView = new TextView(context);
// readView.setText("right-页面");
View rootView = View.inflate(context, R.layout.controller_right, null);
mListView = (ListView) rootView.findViewById(R.id.lv);
prepareData();
RightAdapter rightAdapter = new RightAdapter(context, mDatas);
mListView.setAdapter(rightAdapter);
setListViewHeightBasedOnChildren(mListView);
return rootView;
}
private void prepareData() {
mDatas = new ArrayList<>();
for (int i = 0; i < 20; i++) {
mDatas.add("item_" + i);
}
}
/**
* 在scrollview中完整显示listview
*
* @param listView
*/
public void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
// for (int i = 0; i < listAdapter.getCount(); i++) {
for (int i = 0; i < 10; i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() * 5);// (listAdapter.getCount() - 1));
Log.d(TAG, "params.height: "+params.height);
listView.setLayoutParams(params);
}
}
而在Activity的tab切换事件中只需这样两句代码即可把controller的view动态添加进Activity中:
@Override
public