scrollview嵌套listview产生的滑动冲突(recyclerview)

Android滑动冲突主要有两种方法:

1、外部拦截法

 

  1. public class ListScrollView extends ScrollView {  
  2.       
  3.     private ListView listView;  
  4.   
  5.     public ListScrollView(Context context, AttributeSet attrs) {  
  6.         super(context, attrs);  
  7.     }  
  8.   
  9.     public ListScrollView(Context context) {  
  10.         super(context);  
  11.     }  
  12.       
  13.     /** 
  14.      * 覆写onInterceptTouchEvent方法,点击操作发生在ListView的区域的时候, 
  15.      * 返回false让ScrollView的onTouchEvent接收不到MotionEvent,而是把Event传到下一级的控件中 
  16.      */  
  17.     @Override  
  18.     public boolean onInterceptTouchEvent(MotionEvent ev) {  
  19.         // TODO Auto-generated method stub  
  20.         if (listView != null && checkArea(listView, ev)) {  
  21.             return false;  
  22.         }  
  23.         return super.onInterceptTouchEvent(ev);  
  24.     }  
  25.       
  26.     /** 
  27.      *  测试view是否在点击范围内 
  28.      * @param locate 
  29.      * @param v 
  30.      * @return 
  31.      */  
  32.     private boolean checkArea(View v, MotionEvent event){  
  33.         float x = event.getRawX();  
  34.         float y = event.getRawY();  
  35.         int[] locate = new int[2];  
  36.         v.getLocationOnScreen(locate);  
  37.         int l = locate[0];  
  38.         int r = l + v.getWidth();  
  39.         int t = locate[1];  
  40.         int b = t + v.getHeight();  
  41.         if (l < x && x < r && t < y && y < b) {  
  42.             return true;  
  43.         }  
  44.         return false;  
  45.     }  
  46.   
  47.     public ListView getListView() {  
  48.         return listView;  
  49.     }  
  50.   
  51.     public void setListView(ListView listView) {  
  52.         this.listView = listView;  
  53.     }  
  54. }  

2、内部拦截法

 

 
  1. listView.setOnTouchListener(new OnTouchListener() {

  2.  
  3. public boolean onTouch(View v, MotionEvent event) {

  4. // TODO Auto-generated method stub

  5. listView.getParent().requestDisallowInterceptTouchEvent(true);

  6. return false;

  7. }

  8. });


同时要保证父布局不能拦截actiondown事件,否则所有事件都会交给父布局,即父布局重写onInterceptTouchEvent为:

 

 

 
  1. @Override

  2. public boolean onInterceptTouchEvent(MotionEvent e) {

  3. int action = e.getAction();

  4. if(action==MotionEvent.ACTION_DOWN) {

  5. return false;

  6. }

  7. return super.onInterceptTouchEvent(e);

  8. }

 

参考Android开发艺术探索

 

彻底解决问题(完美版)

 

 

外层ScrollView,内嵌ListView,都是垂直方向。采用内部拦截法,实现ListView能滚动时则让ListView处理,当ListView滑到顶部或者底部不能滑动时让ScrollView处理

布局 上面有一段文本,中间是ListView,下面还有一段文本

 
  1. <?xml version="1.0" encoding="utf-8"?>

  2. <org.icegeneral.scroll.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"

  3. android:id="@+id/sv"

  4. android:layout_width="match_parent"

  5. android:layout_height="match_parent">

  6.  
  7. <LinearLayout

  8. android:layout_width="match_parent"

  9. android:layout_height="wrap_content"

  10. android:orientation="vertical">

  11.  
  12. <TextView

  13. android:layout_width="match_parent"

  14. android:layout_height="wrap_content"

  15. android:text="AAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA\nAAAAAAAAAA" />

  16.  
  17. <org.icegeneral.scroll.MyListView

  18. android:id="@+id/lv"

  19. android:layout_width="match_parent"

  20. android:layout_height="500dp"

  21. android:background="#888888">

  22.  
  23. </org.icegeneral.scroll.MyListView>

  24.  
  25. <TextView

  26. android:layout_width="match_parent"

  27. android:layout_height="wrap_content"

  28. android:text="BBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB\nBBBBBBBBBB" />

  29. </LinearLayout>

  30. </org.icegeneral.scroll.MyScrollView>

MyScrollView
ACTION_DOWN必须让给ListView,ListView才能收到ACTION_MOVE,ListView才能判断自己是否还能滚动
第二点要注意的是:因为ACTION_DOWN让给ListView,那么ACTION_DOWN就无法进入ScrollView的onTouchEvent, 但是ScrollView的滚动需要在ACTION_DOWN阶段做一些准备,所以主动调用了一次

 
  1. public class MyScrollView extends ScrollView {

  2.  
  3. public MyScrollView(Context context, AttributeSet attrs) {

  4. super(context, attrs);

  5. }

  6.  
  7. @Override

  8. public boolean onInterceptTouchEvent(MotionEvent ev) {

  9. if (ev.getAction() == MotionEvent.ACTION_DOWN) {

  10. onTouchEvent(ev);

  11. return false;

  12. }

  13. return true;

  14. }

  15.  
  16. }

MyListView

 
  1. public class MyListView extends ListView {

  2.  
  3. public MyListView(Context context, AttributeSet attrs) {

  4. super(context, attrs);

  5. }

  6.  
  7. private float lastY;

  8.  
  9. @RequiresApi(api = Build.VERSION_CODES.KITKAT)

  10. @Override

  11. public boolean dispatchTouchEvent(MotionEvent ev) {

  12. if (ev.getAction() == MotionEvent.ACTION_DOWN) {

  13. getParent().getParent().requestDisallowInterceptTouchEvent(true);

  14. } else if (ev.getAction() == MotionEvent.ACTION_MOVE) {

  15. if (lastY > ev.getY()) {

  16. // 如果是向上滑动,且不能滑动了,则让ScrollView处理

  17. if (!canScrollList(1)) {

  18. getParent().getParent().requestDisallowInterceptTouchEvent(false);

  19. return false;

  20. }

  21. } else if (ev.getY() > lastY) {

  22. // 如果是向下滑动,且不能滑动了,则让ScrollView处理

  23. if (!canScrollList(-1)) {

  24. getParent().getParent().requestDisallowInterceptTouchEvent(false);

  25. return false;

  26. }

  27. }

  28. }

  29. lastY = ev.getY();

  30. return super.dispatchTouchEvent(ev);

  31. }

  32.  
  33. }

Activity

 
  1. protected void onCreate(Bundle savedInstanceState) {

  2. ....

  3. scrollView.smoothScrollTo(0, 0);

  4. }


ACTION_DOWN的特殊性
解释下为什么ListView的ACTION_UP无需调用
getParent().getParent().requestDisallowInterceptTouchEvent(false)
来看下ViewGroup源码

 
  1. @Override

  2. public boolean dispatchTouchEvent(MotionEvent ev) {

  3. ...

  4. boolean handled = false;

  5. if (onFilterTouchEventForSecurity(ev)) {

  6. final int action = ev.getAction();

  7. final int actionMasked = action & MotionEvent.ACTION_MASK;

  8.  
  9. if (actionMasked == MotionEvent.ACTION_DOWN) {

  10. cancelAndClearTouchTargets(ev);

  11. resetTouchState(); //重点是这句

  12. }

  13.  
  14. // Check for interception.

  15. final boolean intercepted;

  16. if (actionMasked == MotionEvent.ACTION_DOWN

  17. || mFirstTouchTarget != null) {

  18. final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

  19. if (!disallowIntercept) {

  20. intercepted = onInterceptTouchEvent(ev);

  21. ev.setAction(action); // restore action in case it was changed

  22. } else {

  23. intercepted = false;

  24. }

  25. } else {

  26. intercepted = true;

  27. }

  28. ...

  29. }

  30. ...

  31. }

  32.  
  33. private void resetTouchState() {

  34. clearTouchTargets();

  35. resetCancelNextUpFlag(this);

  36. mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; //移除FLAG_DISALLOW_INTERCEPT

  37. mNestedScrollAxes = SCROLL_AXIS_NONE;

  38. }

所以如果是ACTION_DOWN,会调用resetTouchState(),移除FLAG_DISALLOW_INTERCEPT,所以ACTION_DOWN时,parent一定会进入onInterceptTouchEvent

其他
其实现在都用RecyclerView代替ListView,ScrollView嵌套RecyclerView时,对于手势已经支持得很好,不必自己处理冲突。这个在demo里也有写,做个对比

代码



作者:风风风筝
链接:http://www.jianshu.com/p/2b038cd9ac14
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

 

问题一:非常感谢博主,解决了我的问题,不过你在 ScrollView 的 onInterceptTouchEvent 的末尾 return true 会导致 ScrollView 里面的其他 View 无法再接收到 Event,所以还是应该改成 return super.onInterceptTouchEvent(ev) ,再次感谢

问题二:博主你好,如果你不在up里面去将request设置成false的话,scrollview上的点击事件就不能响应了

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

 

http://blog.csdn.net/colinandroid/article/details/72770863

http://www.jianshu.com/p/2b038cd9ac14

http://www.cnblogs.com/1426837364qqcom/p/5388902.html

http://blog.csdn.net/lys701/article/details/8755373

http://blog.csdn.net/chaihuasong/article/details/17499799(有关requestDisallowInterceptTouchEvent

http://blog.csdn.net/Wisimer/article/details/49426653

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值