Android:WebView嵌套SwipeRefreshLayout的滚动与缩放异常求助
解决SwipeRefreshLayout与WebView嵌套滚动冲突及缩放后滚动回滚问题
问题概述
- SwipeRefreshLayout嵌套WebView时,WebView向下滚动后尝试向上滚动,会触发SwipeRefreshLayout的刷新动作,而非WebView内部滚动
- 使用第三方NestedScrollWebView解决上述滚动冲突后,WebView页面放大后向右滚动,会自动回滚至初始左侧位置
方案一:原生自定义WebView实现嵌套滚动(无第三方依赖)
通过自定义WebView实现NestedScrollingChild3接口,精确控制滚动事件传递逻辑,同时解决两个核心问题。
代码实现
class FixedNestedScrollWebView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.webViewStyle ) : WebView(context, attrs, defStyleAttr), NestedScrollingChild3 { private val scrollConsumed = IntArray(2) private val offsetInWindow = IntArray(2) private var nestedScrollHelper: NestedScrollingChildHelper = NestedScrollingChildHelper(this) init { isNestedScrollingEnabled = true } override fun onTouchEvent(event: MotionEvent): Boolean { var handled = super.onTouchEvent(event) val action = event.actionMasked when (action) { MotionEvent.ACTION_DOWN -> { startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL or ViewCompat.SCROLL_AXIS_HORIZONTAL, NestedScrollingChildHelper.TYPE_TOUCH) offsetInWindow[0] = 0 offsetInWindow[1] = 0 } MotionEvent.ACTION_MOVE -> { val scrollX = scrollX val scrollY = scrollY val deltaX = event.x.toInt() - offsetInWindow[0] val deltaY = event.y.toInt() - offsetInWindow[1] val consumed = IntArray(2) if (dispatchNestedPreScroll(deltaX, deltaY, consumed, offsetInWindow, NestedScrollingChildHelper.TYPE_TOUCH)) { val remainingX = deltaX - consumed[0] val remainingY = deltaY - consumed[1] if (remainingX != 0 || remainingY != 0) { handled = super.onTouchEvent(event) || performScroll(remainingX, remainingY) } } else { handled = super.onTouchEvent(event) } offsetInWindow[0] += deltaX - consumed[0] offsetInWindow[1] += deltaY - consumed[1] } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { stopNestedScroll(NestedScrollingChildHelper.TYPE_TOUCH) } } return handled } private fun performScroll(deltaX: Int, deltaY: Int): Boolean { val oldScrollX = scrollX val oldScrollY = scrollY scrollBy(-deltaX, -deltaY) return scrollX != oldScrollX || scrollY != oldScrollY } // 实现NestedScrollingChild3接口的必要方法 override fun setNestedScrollingEnabled(enabled: Boolean) { nestedScrollHelper.isNestedScrollingEnabled = enabled } override fun isNestedScrollingEnabled(): Boolean { return nestedScrollHelper.isNestedScrollingEnabled } override fun startNestedScroll(axes: Int, type: Int): Boolean { return nestedScrollHelper.startNestedScroll(axes, type) } override fun startNestedScroll(axes: Int): Boolean { return nestedScrollHelper.startNestedScroll(axes) } override fun stopNestedScroll(type: Int) { nestedScrollHelper.stopNestedScroll(type) } override fun stopNestedScroll() { nestedScrollHelper.stopNestedScroll() } override fun hasNestedScrollingParent(type: Int): Boolean { return nestedScrollHelper.hasNestedScrollingParent(type) } override fun hasNestedScrollingParent(): Boolean { return nestedScrollHelper.hasNestedScrollingParent() } override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?, type: Int) { nestedScrollHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type) } override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?) { nestedScrollHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow) } override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?, type: Int): Boolean { // 仅当WebView无法向上滚动时,才允许父布局处理垂直滚动 val canScrollUp = scrollY > 0 || canScrollVertically(-1) val shouldDispatchVertical = !canScrollUp && dy > 0 // 水平滚动完全由WebView自行处理 if (dx != 0) { consumed?.let { it[0] = 0 it[1] = if (shouldDispatchVertical) dy else 0 } return shouldDispatchVertical } return shouldDispatchVertical && super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type) } override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?): Boolean { return nestedScrollHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) } override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean { return nestedScrollHelper.dispatchNestedFling(velocityX, velocityY, consumed) } override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean { return nestedScrollHelper.dispatchNestedPreFling(velocityX, velocityY) } override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?, type: Int, consumed: IntArray?) { nestedScrollHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed) } }
使用方式
在布局文件中替换原生WebView即可:
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.yourpackage.FixedNestedScrollWebView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/webView"/> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
方案二:修改第三方NestedScrollWebView解决缩放回滚问题
如果已依赖目标仓库,只需修改核心滚动判断逻辑,区分水平/垂直滚动事件:
修改核心代码
找到原仓库NestedScrollWebView中的dispatchNestedPreScroll方法,替换为:
@Override public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) { // 仅当WebView无法向上滚动时,允许父布局处理垂直滚动 boolean canScrollUp = getScrollY() > 0 || canScrollVertically(-1); boolean shouldDispatchVertical = !canScrollUp && dy > 0; // 水平滚动完全由WebView处理,不交给父布局 if (dx != 0) { if (consumed != null) { consumed[0] = 0; consumed[1] = shouldDispatchVertical ? dy : 0; } return shouldDispatchVertical; } return shouldDispatchVertical && super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type); }
说明
原仓库问题在于未区分水平/垂直滚动,导致缩放后WebView的水平滚动被干扰。修改后水平滚动完全由WebView处理,垂直滚动仅在WebView滚动到顶部时才交给SwipeRefreshLayout,同时解决两个问题。
内容的提问来源于stack exchange,提问作者Rahul Mishra
相关产品推荐
相关产品推荐

