1. 内部拦截法:
父容器不拦截事件,所有的事件全部都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交给父容器进行处理。
这种方法和Android中的事件分发机制不一样,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法稍显负责一点。
我们需要重写子元素的dispatchTouchEvent方法。
这种方法的伪代码是:
1 @Override 2 public boolean dispatchTouchEvent(MotionEvent event) { 3 int x = (int) event.getX(); 4 int y = (int) event.getY(); 5 6 switch (event.getAction()) { 7 case MotionEvent.ACTION_DOWN: { 8 parent.requestDisallowInterceptTouchEvent(true); 9 break; 10 } 11 case MotionEvent.ACTION_MOVE: { 12 int deltaX = x - mLastX; 13 int deltaY = y - mLastY; 14 if (父容器需要此类点击事件) { 15 parent.requestDisallowInterceptTouchEvent(false); 16 } 17 break; 18 } 19 case MotionEvent.ACTION_UP: { 20 break; 21 } 22 default: 23 break; 24 } 25 26 mLastX = x; 27 mLastY = y; 28 return super.dispatchTouchEvent(event); 29 }
上面重写的子元素的dispatchTouchEvent方法,这里同时需要重写父容器的onInterceptTouchEvent方法,为什么呢?
那是因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素之中,这样内部拦截法就无法起作用了。
父容器所做的修改如下:
1 @Override 2 public boolean onInterceptTouchEvent(MotionEvent event) { 3 4 int action = event.getAction(); 5 if (action == MotionEvent.ACTION_DOWN) { 6 return false; 7 } else { 8 return true; 9 } 10 }
2. 下面通过一个Demo示例说明:
(1)首先我们创建一个Android工程,如下:
(2)首先我们来到主布局activity_main.xml,如下:
1 <com.himi.viewconflict1.ui.RevealLayout 2 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:tools="http://schemas.android.com/tools" 4 android:layout_width="match_parent" 5 android:layout_height="match_parent" 6 android:orientation="vertical" 7 android:padding="12dp" 8 tools:context="${relativePackage}.${activityClass}" > 9 10 <Button 11 android:id="@+id/button1" 12 style="@style/AppTheme.Button.Green" 13 android:onClick="onButtonClick" 14 android:text="滑动冲突场景1-内部拦截" /> 15 16 </com.himi.viewconflict1.ui.RevealLayout>
(3)接下来来到MainActivity,如下:
1 package com.himi.viewconflict; 2 3 import android.app.Activity; 4 import android.content.Intent; 5 import android.os.Bundle; 6 import android.view.View; 7 8 public class MainActivity extends Activity { 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 setContentView(R.layout.activity_main); 14 } 15 16 17 18 public void onButtonClick(View view) { 19 Intent intent = new Intent(this, DemoActivity_1.class); 20 startActivity(intent); 21 } 22 }
(4)上面很自然地跳转到DemoActivity_2之中,如下:
1 package com.himi.viewconflict1; 2 3 import java.util.ArrayList; 4 5 import com.himi.viewconflict1.ui.HorizontalScrollViewEx2; 6 import com.himi.viewconflict1.ui.ListViewEx; 7 import com.himi.viewconflict1.utils.MyUtils; 8 9 import android.app.Activity; 10 import android.graphics.Color; 11 import android.os.Bundle; 12 import android.util.Log; 13 import android.view.LayoutInflater; 14 import android.view.MotionEvent; 15 import android.view.View; 16 import android.view.ViewGroup; 17 import android.widget.AdapterView; 18 import android.widget.AdapterView.OnItemClickListener; 19 import android.widget.ArrayAdapter; 20 import android.widget.TextView; 21 import android.widget.Toast; 22 23 public class DemoActivity_2 extends Activity { 24 private static final String TAG = "DemoActivity_2"; 25 26 private HorizontalScrollViewEx2 mListContainer; 27 28 @Override 29 protected void onCreate(Bundle savedInstanceState) { 30 super.onCreate(savedInstanceState); 31 setContentView(R.layout.demo_2); 32 Log.d(TAG, "onCreate"); 33 initView(); 34 } 35 36 private void initView() { 37 LayoutInflater inflater = getLayoutInflater(); 38 mListContainer = (HorizontalScrollViewEx2) findViewById(R.id.container); 39 final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels; 40 final int screenHeight = MyUtils.getScreenMetrics(this).heightPixels; 41 for (int i = 0; i < 3; i++) { 42 ViewGroup layout = (ViewGroup) inflater.inflate( 43 R.layout.content_layout2, mListContainer, false); 44 layout.getLayoutParams().width = screenWidth; 45 TextView textView = (TextView) layout.findViewById(R.id.title); 46 textView.setText("page " + (i + 1)); 47 layout.setBackgroundColor(Color 48 .rgb(255 / (i + 1), 255 / (i + 1), 0)); 49 createList(layout); 50 mListContainer.addView(layout); 51 } 52 } 53 54 private void createList(ViewGroup layout) { 55 ListViewEx listView = (ListViewEx) layout.findViewById(R.id.list); 56 ArrayList<String> datas = new ArrayList<String>(); 57 for (int i = 0; i < 50; i++) { 58 datas.add("name " + i); 59 } 60 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, 61 R.layout.content_list_item, R.id.name, datas); 62 listView.setAdapter(adapter); 63 listView.setHorizontalScrollViewEx2(mListContainer); 64 listView.setOnItemClickListener(new OnItemClickListener() { 65 @Override 66 public void onItemClick(AdapterView<?> parent, View view, 67 int position, long id) { 68 Toast.makeText(DemoActivity_2.this, "click item "+position, 69 Toast.LENGTH_SHORT).show(); 70 71 } 72 }); 73 } 74 75 @Override 76 public boolean dispatchTouchEvent(MotionEvent ev) { 77 Log.d(TAG, "dispatchTouchEvent action:" + ev.getAction()); 78 return super.dispatchTouchEvent(ev); 79 } 80 81 @Override 82 public boolean onTouchEvent(MotionEvent event) { 83 Log.d(TAG, "onTouchEvent action:" + event.getAction()); 84 return super.onTouchEvent(event); 85 } 86 }
上面的DemoActivity_2主布局demo_2.xml,如下:
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#ffffff" 6 android:orientation="vertical" > 7 8 <com.himi.viewconflict1.ui.HorizontalScrollViewEx2 9 android:id="@+id/container" 10 android:layout_width="wrap_content" 11 android:layout_height="match_parent" /> 12 13 14 </LinearLayout>
上面使用到HorizontalScrollViewEx2是自定义控件(继承自ViewGroup),如下:
HorizontalScrollViewEx2是父容器,这里需要重写它的onInterceptTouchEvent方法,让父容器不拦截ACTION_DOWN事件。
1 package com.himi.viewconflict1.ui; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.util.Log; 6 import android.view.MotionEvent; 7 import android.view.VelocityTracker; 8 import android.view.View; 9 import android.view.ViewGroup; 10 import android.widget.Scroller; 11 12 public class HorizontalScrollViewEx2 extends ViewGroup { 13 private static final String TAG = "HorizontalScrollViewEx2"; 14 15 private int mChildrenSize; 16 private int mChildWidth; 17 private int mChildIndex; 18 // 分别记录上次滑动的坐标 19 private int mLastX = 0; 20 private int mLastY = 0; 21 22 // 分别记录上次滑动的坐标(onInterceptTouchEvent) 23 private int mLastXIntercept = 0; 24 private int mLastYIntercept = 0; 25 26 private Scroller mScroller; 27 private VelocityTracker mVelocityTracker; 28 29 public HorizontalScrollViewEx2(Context context) { 30 super(context); 31 init(); 32 } 33 34 public HorizontalScrollViewEx2(Context context, AttributeSet attrs) { 35 super(context, attrs); 36 init(); 37 } 38 39 public HorizontalScrollViewEx2(Context context, AttributeSet attrs, 40 int defStyle) { 41 super(context, attrs, defStyle); 42 init(); 43 } 44 45 private void init() { 46 mScroller = new Scroller(getContext()); 47 mVelocityTracker = VelocityTracker.obtain(); 48 } 49 50 @Override 51 public boolean onInterceptTouchEvent(MotionEvent event) { 52 int x = (int) event.getX(); 53 int y = (int) event.getY(); 54 int action = event.getAction(); 55 if (action == MotionEvent.ACTION_DOWN) { 56 mLastX = x; 57 mLastY = y; 58 if (!mScroller.isFinished()) { 59 mScroller.abortAnimation(); 60 return true; 61 } 62 return false; 63 } else { 64 return true; 65 } 66 } 67 68 @Override 69 public boolean onTouchEvent(MotionEvent event) { 70 Log.d(TAG, "onTouchEvent action:" + event.getAction()); 71 mVelocityTracker.addMovement(event); 72 int x = (int) event.getX(); 73 int y = (int) event.getY(); 74 switch (event.getAction()) { 75 case MotionEvent.ACTION_DOWN: { 76 if (!mScroller.isFinished()) { 77 mScroller.abortAnimation(); 78 } 79 break; 80 } 81 case MotionEvent.ACTION_MOVE: { 82 int deltaX = x - mLastX; 83 int deltaY = y - mLastY; 84 Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY); 85 scrollBy(-deltaX, 0); 86 break; 87 } 88 case MotionEvent.ACTION_UP: { 89 int scrollX = getScrollX(); 90 int scrollToChildIndex = scrollX / mChildWidth; 91 Log.d(TAG, "current index:" + scrollToChildIndex); 92 mVelocityTracker.computeCurrentVelocity(1000); 93 float xVelocity = mVelocityTracker.getXVelocity(); 94 if (Math.abs(xVelocity) >= 50) { 95 mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; 96 } else { 97 mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; 98 } 99 mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); 100 int dx = mChildIndex * mChildWidth - scrollX; 101 smoothScrollBy(dx, 0); 102 mVelocityTracker.clear(); 103 Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx); 104 break; 105 } 106 default: 107 break; 108 } 109 110 mLastX = x; 111 mLastY = y; 112 return true; 113 } 114 115 @Override 116 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 117 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 118 int measuredWidth = 0; 119 int measuredHeight = 0; 120 final int childCount = getChildCount(); 121 measureChildren(widthMeasureSpec, heightMeasureSpec); 122 123 int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); 124 int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 125 int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); 126 int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 127 if (childCount == 0) { 128 setMeasuredDimension(0, 0); 129 } else if (heightSpecMode == MeasureSpec.AT_MOST) { 130 final View childView = getChildAt(0); 131 measuredHeight = childView.getMeasuredHeight(); 132 setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight()); 133 } else if (widthSpecMode == MeasureSpec.AT_MOST) { 134 final View childView = getChildAt(0); 135 measuredWidth = childView.getMeasuredWidth() * childCount; 136 setMeasuredDimension(measuredWidth, heightSpaceSize); 137 } else { 138 final View childView = getChildAt(0); 139 measuredWidth = childView.getMeasuredWidth() * childCount; 140 measuredHeight = childView.getMeasuredHeight(); 141 setMeasuredDimension(measuredWidth, measuredHeight); 142 } 143 } 144 145 @Override 146 protected void onLayout(boolean changed, int l, int t, int r, int b) { 147 Log.d(TAG, "width:" + getWidth()); 148 int childLeft = 0; 149 final int childCount = getChildCount(); 150 mChildrenSize = childCount; 151 152 for (int i = 0; i < childCount; i++) { 153 final View childView = getChildAt(i); 154 if (childView.getVisibility() != View.GONE) { 155 final int childWidth = childView.getMeasuredWidth(); 156 mChildWidth = childWidth; 157 childView.layout(childLeft, 0, childLeft + childWidth, 158 childView.getMeasuredHeight()); 159 childLeft += childWidth; 160 } 161 } 162 } 163 164 private void smoothScrollBy(int dx, int dy) { 165 mScroller.startScroll(getScrollX(), 0, dx, 0, 500); 166 invalidate(); 167 } 168 169 @Override 170 public void computeScroll() { 171 if (mScroller.computeScrollOffset()) { 172 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 173 postInvalidate(); 174 } 175 } 176 177 @Override 178 protected void onDetachedFromWindow() { 179 mVelocityTracker.recycle(); 180 super.onDetachedFromWindow(); 181 } 182 }
(5)来到主布局之中,在HorizontalScrollViewEx2之中包含一个子布局content_layout2.xml,如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" > 6 7 <TextView 8 android:id="@+id/title" 9 android:layout_width="wrap_content" 10 android:layout_height="wrap_content" 11 android:layout_marginTop="5dp" 12 android:layout_marginBottom="5dp" 13 android:text="TextView" /> 14 15 <com.himi.viewconflict1.ui.ListViewEx 16 android:id="@+id/list" 17 android:layout_width="match_parent" 18 android:layout_height="match_parent" 19 android:background="#fff4f7f9" 20 android:cacheColorHint="#00000000" 21 android:divider="#dddbdb" 22 android:dividerHeight="1.0px" 23 android:listSelector="@android:color/transparent" /> 24 25 </LinearLayout>
上面的ListViewEx是自定义的控件(继承自ListView),在ListViewEx里面实现了内部拦截法的逻辑,如下:
1 package com.himi.viewconflict1.ui; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.util.Log; 6 import android.view.MotionEvent; 7 import android.widget.ListView; 8 9 public class ListViewEx extends ListView { 10 private static final String TAG = "ListViewEx"; 11 12 private HorizontalScrollViewEx2 mHorizontalScrollViewEx2; 13 14 // 分别记录上次滑动的坐标 15 private int mLastX = 0; 16 private int mLastY = 0; 17 18 public ListViewEx(Context context) { 19 super(context); 20 } 21 22 public ListViewEx(Context context, AttributeSet attrs) { 23 super(context, attrs); 24 } 25 26 public ListViewEx(Context context, AttributeSet attrs, int defStyle) { 27 super(context, attrs, defStyle); 28 } 29 30 public void setHorizontalScrollViewEx2( 31 HorizontalScrollViewEx2 horizontalScrollViewEx2) { 32 mHorizontalScrollViewEx2 = horizontalScrollViewEx2; 33 } 34 35 @Override 36 public boolean dispatchTouchEvent(MotionEvent event) { 37 int x = (int) event.getX(); 38 int y = (int) event.getY(); 39 40 switch (event.getAction()) { 41 case MotionEvent.ACTION_DOWN: { 42 mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true); 43 break; 44 } 45 case MotionEvent.ACTION_MOVE: { 46 int deltaX = x - mLastX; 47 int deltaY = y - mLastY; 48 Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY); 49 if (Math.abs(deltaX) > Math.abs(deltaY)) { 50 mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false); 51 } 52 break; 53 } 54 case MotionEvent.ACTION_UP: { 55 break; 56 } 57 default: 58 break; 59 } 60 61 mLastX = x; 62 mLastY = y; 63 return super.dispatchTouchEvent(event); 64 } 65 66 }
接下来,来到Listview的Item布局content_list_item.xml,如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="50dp" 5 android:gravity="center_vertical" 6 android:orientation="vertical" > 7 8 <TextView 9 android:id="@+id/name" 10 android:layout_width="wrap_content" 11 android:layout_height="wrap_content" 12 android:text="TextView" /> 13 14 </LinearLayout>
(6)最终项目如下:
(7)部署程序到手机上,如下:
3. 示例源码下载