您的位置:首页 > 移动开发

WebView嵌套在ScrollView引起的滑动问题,以及事件分发情况

2015-05-12 19:52 375 查看

前提知识

Touch事件的分发和消费机制

Note: 如果onTouchEvent中返回true, 则表示消费了这个事件,这个事件就不会再传递下去了,如果子View return true, 那么父ViewGroup的onTouchEvent就不会执行。

原因分析

ScrollView的onInterceptTouchEvent源码:

public boolean onInterceptTouchEvent(MotionEvent ev) {
/**
* 从这里可以看出当前Event为MOVE,且mIsBeingDragged(滑动)为true时,就拦截
* Event. 记住是MOVE.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}

//省略代码

switch (action & MotionEvent.ACTION_MASK) {

//省略代码

case MotionEvent.ACTION_DOWN: {
final int y = (int) ev.getY();
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}

/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionY = y;
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();
//从这里可以看出在DOWN事件时,是不拦截DOWN事件的,此DOWN事件会传递
//到子View中,这样子View就可调用onTouchEvent,不过此时是DOWN事件
if (mIsBeingDragged && mScrollStrictSpan == null) {
mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
}
//初始化Scroll
startNestedScroll(SCROLL_AXIS_VERTICAL);
break;
}

//省略代码
}

/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}


ScrollView的onTouchEvent源码:.

public boolean onTouchEvent(MotionEvent ev) {
//省略代码
if (actionMasked == MotionEvent.ACTION_DOWN) {
mNestedYOffset = 0;
}
vtev.offsetLocation(0, mNestedYOffset);

switch (actionMasked) {
//省略代码
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
break;
}

final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) {
deltaY -= mScrollConsumed[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
}
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true;
if (deltaY > 0) {
deltaY -= mTouchSlop;
} else {
deltaY += mTouchSlop;
}
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
mLastMotionY = y - mScrollOffset[1];

final int oldY = mScrollY;
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);

// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
//可以看到在MOVE时,会调用滑动overScrollBy
if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
&& !hasNestedScrollingParent()) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}

final int scrolledDeltaY = mScrollY - oldY;
final int unconsumedY = deltaY - scrolledDeltaY;
if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
mLastMotionY -= mScrollOffset[1];
vtev.offsetLocation(0, mScrollOffset[1]);
mNestedYOffset += mScrollOffset[1];
} else if (canOverscroll) {
final int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
mEdgeGlowTop.onPull((float) deltaY / getHeight(),
ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
mEdgeGlowBottom.onPull((float) deltaY / getHeight(),
1.f - ev.getX(activePointerIndex) / getWidth());
if (!mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
}
if (mEdgeGlowTop != null
&& (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
postInvalidateOnAnimation();
}
}
}
break;
//省略代码
return true;
}


如果把WebView嵌套在ScrollView中,滑动时,函数调用情况为:ScrollView的onInterceptTouchEvent获取到DOWN事件,并传递DOWN事件给WebView, 随后MOVE事件来到,现在经过ScrollView的onInterceptTouchEvent,发现被拦截了,MOVE事件也就不会传递到WebView中,那么调用ScrollView的onTouchEvent,在onTouchEvent中进行实际的scroll动作。总的来说,在Scroll时,就是ScrollView总是响应,WebView不会执行Scroll,这样造成的现象是,一旦WebView中网页放大,那么就无法通过scroll来查看WebView中整个放大后的内容,只能看到当前已经放大的屏幕大小的内容,因此scroll动作在WebView中没有执行,而是在ScrollView中执行了。

解决方案

1.WebView中的Scale动作会被ScrollView影响,解决方案:

自定义一个ScrollView,在onInterceptTouchEvent中检测到当前有两个手指,就是Scale,就不拦截,让WebView来处理这个动作。

public class PoorPriorityScrollView extends ScrollView {

public PoorPriorityScrollView(Context context) {
this(context, null);
}

public PoorPriorityScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public PoorPriorityScrollView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getPointerCount() > 1) {
return false;
}

return super.onInterceptTouchEvent(ev);
}

}


自定义WebView,在onTouchEvent中调用requestDisallowInterceptTouchEvent,请求父ViewGroup不要拦截事件:

public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//首先DOWN事件,无论怎样都会传递到WebView中,这时
//可以调用requestDisallowInterceptTouchEvent,让Scroll
//View不拦截MOVE事件
case MotionEvent.ACTION_DOWN:
getParent().getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
//在MOVE事件中,我们确定两种情况Scroll是需要ScrollView来执行
//如果WebView中的内容滑到顶部,这时就由ScrollView来执行
//Scroll动作。如果WebView中的内容滑到底部,这时就由
//ScrollView来执行Scroll动作。其他情况Scroll动作都由WebView
//来执行。
boolean scroll = true;
if (isTop()) {     //是否滑到顶部
scroll = false;
} else if (isBottom()){    //是否滑到底部
scroll = false;
}
getParent().getParent().requestDisallowInterceptTouchEvent(scroll);

break;
case MotionEvent.ACTION_UP:
getParent().getParent().requestDisallowInterceptTouchEvent(false);
}

return super.onTouchEvent(event);
}

private boolean isBottom() {
float htmlHeight = getContentHeight() * getScale();
float measuredHeight = getMeasuredHeight();
float currentheight = getHeight() + getScrollY();
Log.d("xuchun", htmlHeight + ", " + measuredHeight + ", " + getHeight() + ", " + getScrollY());
return htmlHeight == currentheight;
}

private boolean isTop() {
return getScrollY() == 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐