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; }
相关文章推荐
- 解决双ViewPager嵌套的事件分发和去掉viewpager滑动问题
- ScrollView中若嵌套有滑动的控件(如:WebView,ListView或GridView)焦点问题
- 关于ViewPager被嵌套在ScrollView中不显示以及滑动冲突的问题
- 解决小米系统下ViewPager、ScrollView内嵌套WebView时,Touch事件不响应的问题
- 解决ScrollView嵌套viewpager滑动事件冲突问题
- 解决ScrollView嵌套viewPager中嵌套listView滑动事件冲突问题(水平方向)
- ScrollView中若嵌套有滑动的控件(如:WebView,ListView或GridView),另外该界面中还有其它的控件时,界面不显示最上面控件的问题。
- ScrollView中若嵌套有滑动的控件(如:WebView,ListView或GridView)时的焦点问题
- scrollview 嵌套 recyclerview 滑动惯性消失问题解决,recclerview 滑到底部事件
- ScrollView内嵌套 WebView WebView 拦截滚动事件问题
- ScrollView内嵌套 WebView WebView 拦截滚动事件问题
- android 异常问题 Scrollview中嵌套webview出现大面积空白(第二次打开同一个地址下面才出现空白)
- ScrollView和WebView 嵌套,底部出现大块空白的问题
- 解决ScrollView嵌套ViewPager出现的滑动冲突问题
- 防止viewpager和子view滑动冲突(ScrollView嵌套webview,Listview嵌套Listview 滑动事件冲突)
- Android 解决ScrollView嵌套RecyclerView导致滑动不流畅的问题
- 解决ScrollView嵌套RecyclerView导致滑动不流畅的问题
- ScrollView嵌套ViewPager空白,上下或者左右不能滑动问题
- ScrollView嵌套ListView,GridView,ViewPager,以及这些控件自动滚动到底部问题的解决
- 自定义ViewFlow使用,ScrollView中嵌套ViewFlow滑动问题解决(---My 20150407)