解决 ScrollView 嵌套 ListView 时,高度不正常和滑动冲突的问题

我们在用ScrollView嵌套ListView会两个问题,一个问题是ListView高度不正常,另外一个问题是ListView无法滑动。下面我们就来看看这两个问题怎么解决吧。


第一个问题

ListView只能显示一个Item高度的问题。因为ScrollView在测量ChildView的时候,强制把ChildView的MeasureSpec模式更改为MeasureSpec.UNSPECIFIED,而我们通过查看ListView的onMeasure方法可以发现

[html]  view plain  copy
  1. if (heightMode == MeasureSpec.UNSPECIFIED) {  
  2.     heightSize = mListPadding.top + mListPadding.bottom + childHeight +  
  3.             getVerticalFadingEdgeLength() * 2;  
  4. }  

当heightMode为MeasureSpec.UNSPECIFIED时,ListView的高度会被设置为上下的padding加上一个childView的高度,所以就会出现只显示一个Item高度的问题。

那我们怎么解决呢?


第一种方法,我们可以重写ListView,在onMeasure的时候重新设置高度的MeasureSpec模式。

[java]  view plain  copy
  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.     // MeasureSpec的前两位是模式,所以需要右移两位。  
  4.     heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);  
  5.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  6. }  
这样ListView就是完全展开的状态了。


第二种方法,我们可以在布局里面为ScollView加一个

[html]  view plain  copy
  1. android:fillViewport="true"  
这样ScrollView就会被强制要求测量ChildView并设置模式MeasureSpec.EXACTLY,所以ListView会完全展开。


第二个问题

ScrollView嵌套ListView时,一般我们有两种需求

第一种是ListVIew完全伸展并跟随ScrollView一起滑动,那只要按照上面的解决了伸展的问题, 就实现这种效果了,因为ScrollView默认是拦截ListView的滑动事件的。


第二种是ScrollView不拦截滑动事件,当我们在ListView区域滑动时,由ListView处理滑动事件,只有在ListView已到达顶部还继续向上滑或者ListView已到达底部还继续向下滑时才重新拦截滑动事件。而当我们在非ListView区域滑动时,则直接由ScrollView处理滑动事件,那么我们看看怎么实现这种效果。


首先,我们先了解为什么ListView无法获取到滑动事件,我们先看一下ScrollView的onInterceptTouchEvent方法

[java]  view plain  copy
  1.  @Override  
  2.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  3.         final int action = ev.getAction();  
  4.         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {  
  5.             return true;  
  6.         }  
  7.     省略  
  8.        //........  
  9. }  
不重要的部分被我省略了,mIsBeingDragged是最小滑动像素,超过这个值才被认为是发生滑动了。那从上面的代码我们可以看出的,当发生滑动的第一时间,事件就被ScrollView拦截了,所以ListView不能滑动

但是我们注意到,在MotionEvent.ACTION_MOVE之前是会产生一个MotionEvent.ACTION_DOWN的,那么这个事件是没有被拦截的,并且在滑动未超过最小像素之前,
MotionEvent.ACTION_MOVE也是未被拦截的,那么其实我们是可以在ListView里面接收到MotionEvent.ACTION_DOWN和几个MotionEvent.ACTION_MOVE的。
既然如此,那我们在自定义ListView里面请求ScrollView不要拦截事件不就好了吗。


[java]  view plain  copy
  1. public class MyListview extends ListView{  
  2.   
  3.     private ScrollView mParent;  
  4.   
  5.     private float mDownY;  
  6.   
  7.     public MyListview(Context context) {  
  8.         super(context);  
  9.     }  
  10.   
  11.     public MyListview(Context context, AttributeSet attrs) {  
  12.         super(context, attrs);  
  13.     }  
  14.   
  15.     public MyListview(Context context, AttributeSet attrs, int defStyleAttr) {  
  16.         super(context, attrs, defStyleAttr);  
  17.     }  
  18.   
  19.     public void setParent(ScrollView view){  
  20.         mParent = view;  
  21.     }  
  22.   
  23.   
  24.     //重写该方法 在按下的时候让父容器不处理滑动事件  
  25.     @Override  
  26.     public boolean onTouchEvent(MotionEvent ev) {  
  27.         switch (ev.getAction()) {  
  28.             case MotionEvent.ACTION_DOWN:  
  29.                 setParentScrollAble(false);  
  30.                 mDownY = ev.getY();  
  31.                 break;  
  32.             case MotionEvent.ACTION_MOVE:  
  33.                 //  
  34.                 if (isListViewReachTop() && ev.getY() - mDownY > 0) {  
  35.                     setParentScrollAble(true);  
  36.                 } else if (isListViewReachBottom() && ev.getY() - mDownY < 0) {  
  37.                     setParentScrollAble(true);  
  38.                 }  
  39.                 break;  
  40.             case MotionEvent.ACTION_UP:  
  41.   
  42.             case MotionEvent.ACTION_CANCEL:  
  43.                 setParentScrollAble(true);  
  44.                 break;  
  45.             default:  
  46.                 break;  
  47.         }  
  48.         return super.onTouchEvent(ev);  
  49.     }  
  50.   
  51.   
  52.     /** 
  53.      * @param flag 
  54.      */  
  55.     private void setParentScrollAble(boolean flag) {  
  56.         mParent.requestDisallowInterceptTouchEvent(!flag);  
  57.     }  
  58.   
  59.   
  60.     public boolean isListViewReachTop() {  
  61.         boolean result=false;  
  62.         if(getFirstVisiblePosition()==0){  
  63.             View topChildView = getChildAt(0);  
  64.             if (topChildView != null) {  
  65.                 result=topChildView.getTop()==0;  
  66.             }  
  67.         }  
  68.         return result ;  
  69.     }  
  70.   
  71.     public boolean isListViewReachBottom() {  
  72.         boolean result=false;  
  73.         if (getLastVisiblePosition() == (getCount() - 1)) {  
  74.             View bottomChildView = getChildAt(getLastVisiblePosition() - getFirstVisiblePosition());  
  75.             if (bottomChildView != null) {  
  76.                 result= (getHeight() >= bottomChildView.getBottom());  
  77.             }  
  78.         }  
  79.         return  result;  
  80.     }  
  81. }  
在上面的代码中,我们首先需要把ScrollView设置进来,然后在onTouchEvent中接收到MotionEvent.ACTION_DOWN事件时,请求ScrollView不要拦截滑动事件,这样ListView就可以处理滑动事件了,而在顶部仍然向上滑和在底部仍然向下滑 和 MotionEvent.ACTION_UP 和 MotionEvent_ACTION_CANCEL时,都直接重新把事件给回ScrollView。



[java]  view plain  copy
  1. <pre name="code" class="java"><pre></pre>  
  2. <pre></pre>  
  3. <pre></pre>  
  4.                     </pre>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值