最近项目涉及到一个功能,需要在HorizontalScrollView里面添加一个SeekBar控件用来调节音量。当代码写好之后,自己动手滑动一下,感觉效果特别差。具体表现就是:SeekBar的滑块只有在点击的时候才有作用,而在手指拖动滑动它的时候,SeekBar的滑块并不会移动,而是HorizontalScrollView在移动,这就很尴尬了。
后面经过查看源码和分析,解决了此问题,具体原因如下:
1、首先我们要知道Android触摸事件的传递机制Android事件分发机制。事件首先是从根View传递下来,然后执行dispatchKeyEvent(keyEvent)方法准备把事件传递到它下面的子控件,如果父控件的onInterceptTouchEvent(keyEvent)方法没有返回true,表示不拦截,那么子控件就会得到此事件,以此类推,然后最后的子控件会把事件通过onTouch(keyEvent)方法把事件回传给它的父控件,整个事件传递形成一个“U”型路线。当某个控件把该事件消耗掉之后,即返回的值为true,事件就会终止传递。
2、我们看看HorizontalScrollView在是否拦截事件时候的源码
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
此处非常重要,我们看到如果事件是ACTION_MOVE并且mIsBeingDragged标志位为true的时候,就会返回true,从而拦截掉本次事件
return true;
}
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionX is set to the x value
* of the down event.
*/
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + activePointerId
+ " in onInterceptTouchEvent");
break;
}
final int x = (int) ev.getX(pointerIndex);
final int xDiff = (int) Math.abs(x - mLastMotionX);
if (xDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionX = x;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
if (!inChild((int) x, (int) ev.getY())) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = x;
mActivePointerId = ev.getPointerId(0);
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mIsBeingDragged = !mScroller.isFinished();
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
postInvalidateOnAnimation();
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mLastMotionX = (int) ev.getX(index);
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
这里就可以解释为什么只有ACTION_MOVE事件会被拦截,而其他的事件不会被拦截的原因了。
3、在ViewGroup中有一个方法requestDisallowInterceptTouchEvent(boolean disallowIntercept),当设置它为true的时候,表示不允许父控件拦截事件,于是我们可以设置这个方法为true,让事件传递给SeekBar,这样SeekBar就可以滑动了。