Android中View的滑動衝突——Android開發藝術探索筆記
介紹
相信開發Android的人都會有這種體會:從網上下載的demo啟動並執行好好的,但是只要出現了滑動衝突,Demo就無法正常工作了。但是不用擔心,解決滑動衝突有固定的模式,常見的有內部攔截和外部攔截兩種,只要按照這個模式來就可以順利解決。
常見的滑動衝突情境
樣本
處理規則
對於情境1,處理規則為:當使用者左右滑動時,讓外部的View攔截點擊事件,當使用者上下滑動時,讓內部的View攔截點擊事件。當產生滑動時,根據滑動的起始點與終點座標位置,如果垂直方向滑動距離大,就判斷為垂直滑動,否則判斷為水平滑動。其他兩種情況處理方法相似,都是從業務需求上得出相應的規則。
解決方案外部攔截髮
所有的點擊事件都先經過父容器攔截處理,如果父容器需要攔截就攔截,不需要就傳給內部的View。虛擬碼如下
public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: { if (滿足父容器的攔截要求) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; }
註:
ACTION_DOWN這個事件是不能攔截的,因為一旦攔截後續的事件都會由父容器處理了。
內部攔截法
父容器不攔截任何事件,所有事件都傳給子項目。如果子項目需要此事件就直接消耗,否則就交給父容器進行處理。完成這個功能需要配合requestDisallowInterceptTouchEvent()方法才可。這個方法表示是否讓父容器攔截事件。虛擬碼如下:
public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { parent.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { if (滿足父容器的攔截要求) { parent.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }
注
父容器預設攔截除了ACTION_DOWN以外的其他事件,這樣子當元素調用parent.requestDisallowInterceptTouchEvent(false)時,父元素才能攔截所需的事件。
總結解決滑動衝突有兩種方法,推薦外部攔截法,實現起來簡單。 本文以情境1為例做了講解,情境2,3的做法與1類似,都是根據業務需要制定處理規則。樣本效果的源碼Activity
public class DemoActivity_1 extends Activity { private static final String TAG = "DemoActivity_1"; private HorizontalScrollViewEx mListContainer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.demo_1); Log.d(TAG, "onCreate"); initView(); } private void initView() { LayoutInflater inflater = getLayoutInflater(); mListContainer = (HorizontalScrollViewEx) findViewById(R.id.container); final int screenWidth = MyUtils.getScreenMetrics(this).widthPixels; final int screenHeight = MyUtils.getScreenMetrics(this).heightPixels; for (int i = 0; i < 3; i++) { ViewGroup layout = (ViewGroup) inflater.inflate( R.layout.content_layout, mListContainer, false); layout.getLayoutParams().width = screenWidth; TextView textView = (TextView) layout.findViewById(R.id.title); textView.setText("page " + (i + 1)); layout.setBackgroundColor(Color.rgb(255 / (i + 1), 255 / (i + 1), 0)); createList(layout); mListContainer.addView(layout); } } private void createList(ViewGroup layout) { ListView listView = (ListView) layout.findViewById(R.id.list); ArrayList datas = new ArrayList(); for (int i = 0; i < 50; i++) { datas.add("name " + i); } ArrayAdapter adapter = new ArrayAdapter(this, R.layout.content_list_item, R.id.name, datas); listView.setAdapter(adapter); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Toast.makeText(DemoActivity_1.this, "click item", Toast.LENGTH_SHORT).show(); } }); }}
水平滑動的View
public class HorizontalScrollViewEx extends ViewGroup { private static final String TAG = "HorizontalScrollViewEx"; private int mChildrenSize; private int mChildWidth; private int mChildIndex; // 分別記錄上次滑動的座標 private int mLastX = 0; private int mLastY = 0; // 分別記錄上次滑動的座標(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalScrollViewEx(Context context) { super(context); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { Log.d(TAG, "onInterceptTouchEvent: ACTION_DOWN"); intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; } case MotionEvent.ACTION_MOVE: { Log.d(TAG, "onInterceptTouchEvent: ACTION_MOVE"); int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } Log.d(TAG, "intercepted=" + intercepted); mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { Log.d(TAG, "onTouchEvent: ACTION_DOWN"); if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { Log.d(TAG, "onTouchEvent: ACTION_MOVE"); int deltaX = x - mLastX; int deltaY = y - mLastY; Log.d(TAG, "onTouchEvent: deltaX" + deltaX); scrollBy(-deltaX, 0); break; } case MotionEvent.ACTION_UP: { int scrollX = getScrollX(); int scrollToChildIndex = scrollX / mChildWidth; mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); //滑的速度到達閾值就認為需要進入下一頁 if (Math.abs(xVelocity) >= 100) { mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; } else { //滑動的距離超過一半,就進入下一頁 mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } //保證在0頁和最後一頁滑動時不會越界 mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); //沒有達到進入下一頁的要求,恢複原樣 int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 0); Log.d(TAG, "onTouchEvent: dx = " + dx); mVelocityTracker.clear(); break; } default: break; } mLastX = x; mLastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight()); } else if (widthSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measuredWidth, heightSpaceSize); } else { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measuredHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); }}