从原则上来说,不应该在一个可滚动的视图中再放置一个可滚动的视图,即滚动视图的嵌套。原因很简单,如果不加以限制,当移动事件发生时,触摸点处于有嵌套关系的多个滚动视图的交叠部分,安卓无法区分该事件应该由哪一个滚动视图消费。所以,如果不是必须,可以使用其它的一些替代方式。比如用LinearLayout取代ListView放在一个ScrollView中,或者将ScrollView作为一个独立的View充当ListView的header。
如果必须要进行嵌套,那么可以采用如下的方式:
ListView lv = (ListView)findViewById(R.id.listview);
lv.setOnTouchListener(newListView.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
v.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
v.onTouchEvent(event);
return true;
}
});
作为父控件的ScrollView重写了onInterceptTouchEvent方法,在其静止状态时是不拦截Action_Down事件的,此时在ListView所处区域触碰屏幕产生的Action_Down事件会顺利的传递给ListView的onDispatchTouchEvent,而ListView虽然是一个ViewGroup,但是使用的时候不会往里面放子视图,而是通过适配器来加载条目,所以ListView的Action_Down事件不会被子视图消费走,最终会回到View的onDispatchTouchEvent,此时如果ListView上绑定了OnTouchListener,就会触发OnTouchListener里面的onTouch方法,并且如果onTouch方法返回true,就不会去执行ListView的onTouchEvent。所以,我们在onTouch里面要做三件事情:
1) 不允许作为父控件的ScrollView拦截任何事件
2) 手动调用自己的onTouchEvent方法。ListView重写了onTouchEvent方法,里面有大量自己的逻辑
3) 返回true
这样一来,所有的Action_Down后续事件都会经ScrollView的onDispatchToucEvent方法传递到ListView的OnTouchListener中来。只到收到了Action_Up事件,这意味着一次完整事件的结束,因此在遇到Action_Up事件时,要恢复ScrollView的拦截能力。
这里还有一个额外的问题,就是ListView在ScrollView中显示的高度,只有一行。如果需要调整,可以写一个工具方法:
public static voidsetListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if(listAdapter == null)
return;
int desiredWidth = MeasureSpec.makeMeasureSpec(
listView.getWidth(), MeasureSpec.UNSPECIFIED);
inttotalHeight = 0;
Viewview = null;
for(int i = 0; i < listAdapter.getCount(); i++) {
view = listAdapter.getView(i, view, listView);
if (i == 0)
view.setLayoutParams(
new ViewGroup.LayoutParams(
desiredWidth, LayoutParams.WRAP_CONTENT));
view.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() *(listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
方法的作用就是将ListView的高度设定为可以一次性的将adapter中的所有条目都展示出来,通过设定LayoutParams来“覆盖”父控件原先分配的内容,设定自己希望的宽度和高度,设定完毕后申请一次重新布局。但是这样做完之后, ListView已经不用滚动机制了,因为它所有的内容都显示出来了,这样的ListView就好像一个“LinearLayout”,这么做实际上是摒弃了ListView的性能优点的。所以可以折中一下,为ListView提供一个指定的高度。
public static voidsetListViewHeightBasedOnChildren(ListView listView,int listviewHeight) {
ListAdapterlistAdapter = listView.getAdapter();
if(listAdapter == null)
return;
ViewGroup.LayoutParamsparams = listView.getLayoutParams();
params.height= listviewHeight;
listView.setLayoutParams(params);
listView.requestLayout();
}
通过该方法,可以将ListView的高度设置为我们指定的高度,比如屏幕高度的1/3。
setListViewHeightBasedOnChildren(lv,getResources().getDisplayMetrics().heightPixels/3);
完整的MainActivity代码如下:
public class MainActivity extends Activity {
privateListView lv;
publicString[] strs ={"星期一","星期二","星期三","星期四","星期五","星期六","星期日","星期一","星期二","星期三","星期四","星期五","星期六","星期日","星期一","星期二","星期三","星期四","星期五","星期六","星期日"};
publicstatic void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapterlistAdapter = listView.getAdapter();
if(listAdapter == null)
return;
intdesiredWidth = MeasureSpec.makeMeasureSpec(
listView.getWidth(),MeasureSpec.UNSPECIFIED);
inttotalHeight = 0;
Viewview = null;
for(int i = 0; i < listAdapter.getCount(); i++) {
view= listAdapter.getView(i, view, listView);
if(i == 0)
view.setLayoutParams(newViewGroup.LayoutParams(
desiredWidth,LayoutParams.WRAP_CONTENT));
view.measure(desiredWidth,MeasureSpec.UNSPECIFIED);
totalHeight+= view.getMeasuredHeight();
}
ViewGroup.LayoutParamsparams = listView.getLayoutParams();
params.height= totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
listView.setLayoutParams(params);
listView.requestLayout();
}
publicstatic void setListViewHeightBasedOnChildren(ListView listView,int listviewHeight){
ListAdapterlistAdapter = listView.getAdapter();
if(listAdapter == null)
return;
ViewGroup.LayoutParamsparams = listView.getLayoutParams();
params.height= listviewHeight;
listView.setLayoutParams(params);
listView.requestLayout();
}
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
lv= (ListView) findViewById(R.id.listview);
lv.setAdapter(newArrayAdapter<String>(this, android.R.layout.simple_list_item_1,strs));
lv.setOnTouchListener(newOnTouchListener() {
@Override
publicboolean onTouch(View v, MotionEvent event) {
intaction = event.getAction();
switch(action) {
caseMotionEvent.ACTION_DOWN:
v.getParent().requestDisallowInterceptTouchEvent(true);
break;
caseMotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
v.onTouchEvent(event);
returntrue;
}
});
setListViewHeightBasedOnChildren(lv,getResources().getDisplayMetrics().heightPixels/3);
}
@Override
publicboolean onCreateOptionsMenu(Menu menu) {
//Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main,menu);
returntrue;
}
}
布局文件如下:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ScrollView
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="XXXXXXXXXXXXXXXXXXXXXXXX" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="XXXXXXXXXXXXXXXXXXXXXXXX" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="XXXXXXXXXXXXXXXXXXXXXXXX" />
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</ListView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="XXXXXXXXXXXXXXXXXXXXXXXX" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="XXXXXXXXXXXXXXXXXXXXXXXX" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="XXXXXXXXXXXXXXXXXXXXXXXX" />
</LinearLayout>
</ScrollView>
</RelativeLayout>