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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM