NestedWebView可与ScrollingViewBehavior一起正常使用 [英] NestedWebView working properly with ScrollingViewBehavior

查看:140
本文介绍了NestedWebView可与ScrollingViewBehavior一起正常使用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序,其中两个活动扩展了相同的抽象-一个使用 WebView ,另一个使用 ViewPager TabLayout RecyclerView s-在此级别,我已经实现了具有滚动/切换功能的共享显示/隐藏工具栏:



app:layout_scrollFlags = scroll | snap | enterAlways



WebView 正在推出 工具栏就像 ViewPager 中的任何选项卡下的 RecyclerView (保持 TabLayout 可见),在两种情况下,任何滚动到顶部都会带回工具栏。就像在此示例上一样:





我已经从指南中借来的GIF,这建议(也包括其他所有建议)我想要的行为是默认的,只带有 scroll 标志(可能还有 snap )。因此,我删除了 enterAlways ,而我的本地 活动可以按预期的方式开始工作。 WebView -完全没有变化...



我怀疑此错误是由我的<$引起的c $ c> NestedWebView ,我目前正在使用 。因此,我尝试使用以下方法替换此类:



marshi -不可滚动的Webview内容(工具栏显示/隐藏)



takahirom -即使1px几乎立即向上滚动(有点突然的行为,但在触摸过程中),工具栏也会进入



感谢-无嵌套滚动(固定工具栏),也带有未注释的 setNestedScrollingEnabled(true);



如何实现第二个GIF 工具栏行为与 WebView 合作?

解决方案

请阅读底部和其他答案/评论!不要使用此类,也请尽量避免使用 WebView 。您迟早会遇到麻烦,维护 WebView 真是地狱...



我们发现 NestedWebView 的几种实现方式很少,并注意到它们都已过时,并且在当前使用第三版时实现了 NestedScrollingChild 。该接口的接口: NestedScrollingChild3 androidx )。因此,我介绍了 NestedSrcollView 的当前版本,并添加了一些新的方法处理,现在可以使用了。确切地说-我试图在整个屏幕上实现平滑滚动,就像 WebView 会填充整个屏幕和工具栏是网页内容的一部分(始终位于顶部)。 快照也在工作。明智地使用

  import android.annotation.SuppressLint; 
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.webkit.WebView;
import android.widget.OverScroller;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.NestedScrollingChild3;
import androidx.core.view.NestedScrollingChildHelper;
import androidx.core.view.ViewCompat;

/ **
*与CoordinatorLayout兼容的WebView by snachmsm
*基于NestedScrollView的基于设计库androidx v1.0.1的实现
* /
public class NestedWebView扩展了WebView的实现NestedScrollingChild3 {

private static final String TAG = NestedWebView;
private static final int INVALID_POINTER = -1;

private final int [] mScrollOffset = new int [2];
private final int [] mScrollConsumed = new int [2];

private int mLastMotionY;
private NestedScrollingChildHelper mChildHelper;
私有布尔值mIsBeingDragged = false;
私人VelocityTracker mVelocityTracker;
private int mTouchSlop;
private int mActivePointerId = INVALID_POINTER;
private int mNestedYOffset;
私人OverScroller mScroller;
private int mMinimumVelocity;
private int mMaximumVelocity;
private int mLastScrollerY;

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

public NestedWebView(Context context,AttributeSet attrs){
this(context,attrs,android.R.attr.webViewStyle);
}

public NestedWebView(Context context,AttributeSet attrs,int defStyleAttr){
super(context,attrs,defStyleAttr);
setOverScrollMode(WebView.OVER_SCROLL_NEVER);
initScrollView();
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}

private void initScrollView(){
mScroller = new OverScroller(getContext());
最终的ViewConfiguration配置= ViewConfiguration.get(getContext());
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}

@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
final int action = ev.getAction();
if((action == MotionEvent.ACTION_MOVE)&&(mIsBeingDragged)){//最常见的
返回true;
}

开关(action& MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_MOVE:
final int activePointerId = mActivePointerId;
if(activePointerId == INVALID_POINTER){
休息;
}

final int pointerIndex = ev.findPointerIndex(activePointerId);
if(pointerIndex == -1){
Log.e(TAG,无效的pointerId = + activePointerId
+在onInterceptTouchEvent中));
休息时间;
}

final int y =(int)ev.getY(pointerIndex);
final int yDiff = Math.abs(y-mLastMotionY);
if(yDiff> mTouchSlop
&&(getNestedScrollAxes()& ViewCompat.SCROLL_AXIS_VERTICAL)== 0){
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
mNestedYOffset = 0;
最终的ViewParent父对象= getParent();
if(parent!= null){
parent.requestDisallowInterceptTouchEvent(true);
}
}
休息;
case MotionEvent.ACTION_DOWN:
mLastMotionY =(int)ev.getY();
mActivePointerId = ev.getPointerId(0);

initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);

mScroller.computeScrollOffset();
mIsBeingDragged =!mScroller.isFinished();

startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
休息时间;
例MotionEvent.ACTION_CANCEL:
例MotionEvent.ACTION_UP:
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
recycleVelocityTracker();
if(mScroller.springBack(getScrollX(),getScrollY(),0,0,0,getScrollRange())){
ViewCompat.postInvalidateOnAnimation(this);
}
stopNestedScroll();
休息时间;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
休息时间;
}

return mIsBeingDragged;
}

@SuppressLint( ClickableViewAccessibility)
@Override
public boolean onTouchEvent(MotionEvent ev){
initVelocityTrackerIfNotExists();

MotionEvent vtev = MotionEvent.obtain(ev);

最终int actionMasked = ev.getActionMasked();

if(actionMasked == MotionEvent.ACTION_DOWN){
mNestedYOffset = 0;
}
vtev.offsetLocation(0,mNestedYOffset);

开关(actionMasked){
case MotionEvent.ACTION_DOWN:
if(((mIsBeingDragged =!mScroller.isFinished())){
final ViewParent parent = getParent( );
if(parent!= null){
parent.requestDisallowInterceptTouchEvent(true);
}
}

if(!mScroller.isFinished()){
abortAnimatedScroll();
}

mLastMotionY =(int)ev.getY();
mActivePointerId = ev.getPointerId(0);
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL,ViewCompat.TYPE_TOUCH);
休息时间;
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if(activePointerIndex == -1){
Log.e(TAG,无效的指标id = + mActivePointerId + in onTouchEvent);
休息时间;
}

final int y =(int)ev.getY(activePointerIndex);
int deltaY = mLastMotionY-y;
if(dispatchNestedPreScroll(0,deltaY,mScrollConsumed,mScrollOffset,
ViewCompat.TYPE_TOUCH)){
deltaY-= mScrollConsumed [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){
mLastMotionY = y-mScrollOffset [1];

final int oldY = getScrollY();
最终int范围= getScrollRange();

//调用overScrollByCompat将调用onOverScrolled,而
//调用onScrollChanged(如果适用)。
if(overScrollByCompat(0,deltaY,0,oldY,0,range,0,
0,true)&&!hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)){
mVelocityTracker.clear ();
}

final int scrolledDeltaY = getScrollY()-oldY;
final int unconsumedY = deltaY-scrolledDeltaY;

mScrollConsumed [1] = 0;

dispatchNestedScroll(0,scrolledDeltaY,0,unconsumedY,mScrollOffset,
ViewCompat.TYPE_TOUCH,mScrollConsumed);

mLastMotionY-= mScrollOffset [1];
mNestedYOffset + = mScrollOffset [1];
}
休息;
案子MotionEvent.ACTION_UP:
最终的VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000,mMaximumVelocity);
int initialVelocity =(int)velocityTracker.getYVelocity(mActivePointerId);
if(((Math.abs(initialVelocity)> mMinimumVelocity)){
if(!dispatchNestedPreFling(0,-initialVelocity)){
dispatchNestedFling(0,-initialVelocity,true);
fling(-initialVelocity);
}
} else if(mScroller.springBack(getScrollX(),getScrollY(),0,0,0,
getScrollRange())){
ViewCompat.postInvalidateOnAnimation(this );
}
mActivePointerId = INVALID_POINTER;
endDrag();
休息时间;
case MotionEvent.ACTION_CANCEL:
if(mIsBeingDragged){
if(mScroller.springBack(getScrollX(),getScrollY(),0,0,0,
getScrollRange()) ){
ViewCompat.postInvalidateOnAnimation(this);
}
}
mActivePointerId = INVALID_POINTER;
endDrag();
休息时间;
case MotionEvent.ACTION_POINTER_DOWN:
最终int索引= ev.getActionIndex();
mLastMotionY =(int)ev.getY(index);
mActivePointerId = ev.getPointerId(index);
休息时间;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionY =(int)ev.getY(ev.findPointerIndex(mActivePointerId));
休息时间;
}

if(mVelocityTracker!= null){
mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return super.onTouchEvent(ev);
}

private void abortAnimatedScroll(){
mScroller.abortAnimation();
stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
}

private void endDrag(){
mIsBeingDragged = false;

recycleVelocityTracker();
stopNestedScroll();
}

私人无效onSecondaryPointerUp(MotionEvent ev){
final int pointerIndex =(ev.getAction()& MotionEvent.ACTION_POINTER_INDEX_MASK)
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if(pointerId == mActivePointerId){
final int newPointerIndex = pointerIndex == 0吗? 1:0;
mLastMotionY =(int)ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if(mVelocityTracker!= null){
mVelocityTracker.clear();
}
}
}

private void fling(int velocityY){
int height = getHeight();
mScroller.fling(getScrollX(),getScrollY(),//开始
0,velocityY,//速度
0,0,// x
Integer.MIN_VALUE,Integer .MAX_VALUE,// y
0,height / 2);
runAnimatedScroll(true);
}

private void runAnimatedScroll(boolean joinInNestedScrolling){
if(participateInNestedScrolling){
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL,ViewCompat.TYPE_NON_TOUCH);
} else {
stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
}
mLastScrollerY = getScrollY();
ViewCompat.postInvalidateOnAnimation(this);
}

@Override
公共无效requestDisallowInterceptTouchEvent(boolean disallowIntercept){
if(disallowIntercept){
recycleVelocityTracker();
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}

私人无效initOrResetVelocityTracker(){
if(mVelocityTracker == null){
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
}

私人无效initVelocityTrackerIfNotExists(){
if(mVelocityTracker == null){
mVelocityTracker = VelocityTracker.obtain();
}
}

private void recycleVelocityTracker(){
if(mVelocityTracker!= null){
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}

@Override
受保护的布尔值overScrollBy(int deltaX,int deltaY,
int scrollX,int scrollY,
int scrollRangeX ,int scrollRangeY,
int maxOverScrollX,int maxOverScrollY,
boolean isTouchEvent){
//这会导致两次滚动调用(速度提高一倍),但是此WebView不会滚动过度
//所有过度滚动都会传递到appbar,因此在拖动
时将其注释掉,如果(!mIsBeingDragged)
overScrollByCompat(deltaX,deltaY,scrollX,scrollY,scrollRangeX,scrollRangeY,
maxOverScrollX,maxOverScrollY, isTouchEvent);
//如果没有此调用,则在更改URL或用户选择输入
时,webview不会滚动到顶部//( adjustResize时,webview应该会移动一点,使输入仍在视口中)
返回true;
}

int getScrollRange(){
//使用webview的滚动范围而不是像NestedScrollView那样使用子级。
return computeVerticalScrollRange();
}

@Override
public boolean isNestedScrollingEnabled(){
return mChildHelper.isNestedScrollingEnabled();
}

@Override
public void setNestedScrollingEnabled(启用布尔值){
mChildHelper.setNestedScrollingEnabled(启用);
}

@Override
public boolean startNestedScroll(int axes,int type){
return mChildHelper.startNestedScroll(axes,type);
}

@Override
public boolean startNestedScroll(int axes){
return startNestedScroll(axes,ViewCompat.TYPE_TOUCH);
}

@Override
public void stopNestedScroll(int type){
mChildHelper.stopNestedScroll(type);
}

@Override
public void stopNestedScroll(){
stopNestedScroll(ViewCompat.TYPE_TOUCH);
}

@Override
public boolean hasNestedScrollingParent(int type){
return mChildHelper.hasNestedScrollingParent(type);
}

@Override
public boolean hasNestedScrollingParent(){
return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
}

@Override
public boolean dispatchNestedScroll(int dxConsumed,int dyConsumed,int dxUnconsumed,int dyUnconsumed,
int [] offsetInWindow){
返回dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,
offsetInWindow,ViewCompat.TYPE_TOUCH);
}

@Override
public boolean dispatchNestedScroll(int dxConsumed,int dyConsumed,int dxUnconsumed,int dyUnconsumed,
int [] offsetInWindow,int type){
return mChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,
offsetInWindow,type);
}

@Override
public void dispatchNestedScroll(int dxConsumed,int dyConsumed,int dxUnconsumed,int dyUnconsumed,
@Nullable int [] offsetInWindow,int type,@ NonNull int []已消耗){
mChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,
offsetInWindow,type,consumed);
}

@Override
public boolean dispatchNestedPreScroll(int dx,int dy,int []消耗,int [] offsetInWindow){
return dispatchNestedPreScroll(dx,dy ,消耗,offsetInWindow,ViewCompat.TYPE_TOUCH);
}


@Override
public boolean dispatchNestedPreScroll(int dx,int dy,int []消耗,int [] offsetInWindow,int类型){
返回mChildHelper.dispatchNestedPreScroll(dx,dy,消耗,offsetInWindow,类型);
}

@Override
公共布尔值dispatchNestedFling(float velocityX,float velocityY,布尔消耗){
return mChildHelper.dispatchNestedFling(velocityX,velocityY,false);
}

@Override
public boolean dispatchNestedPreFling(float velocityX,float velocityY){
return mChildHelper.dispatchNestedPreFling(velocityX,velocityY);
}

@Override
public int getNestedScrollAxes(){
return ViewCompat.SCROLL_AXIS_VERTICAL;
}

@Override
public void computeScroll(){
if(mScroller.isFinished()){
return;
}

mScroller.computeScrollOffset();
final int y = mScroller.getCurrY();
int未消耗= y-mLastScrollerY;
mLastScrollerY = y;

//嵌套滚动前通过
mScrollConsumed [1] = 0;
dispatchNestedPreScroll(0,未消耗,mScrollConsumed,null,
ViewCompat.TYPE_NON_TOUCH);
未消耗-= mScrollConsumed [1];


if(unconsumed!= 0){
//内部滚动
final int oldScrollY = getScrollY();
overScrollByCompat(0,未消耗,getScrollX(),oldScrollY,0,getScrollRange(),
0,0,false);
final int scrolledByMe = getScrollY()-oldScrollY;
未消费-= scrolledByMe;

//嵌套滚动过关
mScrollConsumed [1] = 0;
dispatchNestedScroll(0,0,0,未消耗,mScrollOffset,
ViewCompat.TYPE_NON_TOUCH,mScrollConsumed);
未消耗-= mScrollConsumed [1];
}

if(unconsumed!= 0){
abortAnimatedScroll();
}

if(!mScroller.isFinished()){
ViewCompat.postInvalidateOnAnimation(this);
}
}

//从NestedScrollView复制而来,看起来像这样,保留了与滚动相关的代码,也许将来使用
私有布尔值overScrollByCompat(int deltaX,int deltaY,
int scrollX,int scrollY,
int scrollRangeX,int scrollRangeY,
int maxOverScrollX,int maxOverScrollY,
boolean isTouchEvent){
final int overScrollMode = getOverScrollMode();
最终布尔值canScrollHorizo​​ntal =
computeHorizo​​ntalScrollRange()> computeHorizo​​ntalScrollExtent();
final boolean canScrollVertical =
computeVerticalScrollRange()> computeVerticalScrollExtent();
最终布尔值overScrollHorizo​​ntal = overScrollMode == View.OVER_SCROLL_ALWAYS
|| (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS& canampHorizo​​ntal);
final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
|| (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS& canampVertical);

int newScrollX = scrollX + deltaX;
if(!overScrollHorizo​​ntal){
maxOverScrollX = 0;
}

int newScrollY = scrollY + deltaY;
if(!overScrollVertical){
maxOverScrollY = 0;
}

//钳制值(如果处于极限)并记录
final int = -maxOverScrollX;
final int right = maxOverScrollX + scrollRangeX;
final int top = -maxOverScrollY;
final int bottom = maxOverScrollY + scrollRangeY;

布尔钳位X =假;
if(newScrollX> right){
newScrollX = right;
lampedX = true;
}否则,如果(newScrollX< left){
newScrollX = left;
lampedX = true;
}

布尔值夹紧Y =假;
if(newScrollY> bottom){
newScrollY = bottom;
lampedY = true;
}否则,如果(newScrollY< top){
newScrollY = top;
lampedY = true;
}

if(clampedY&!hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)){
mScroller.springBack(newScrollX,newScrollY,0,0,0,getScrollRange() );
}

onOverScrolled(newScrollX,newScrollY,clampedX,clampedY);

返回钳位X ||夹紧
}
}

编辑:此类有很大的缺陷!此视图消耗所有滚动事件,将其分配到工具栏协调器或它永远不会拥有嵌套滚动div / view的HTML内容的BUT。例如如果您有自己的下拉列表(或任何自滚动视图)实现,则不会用手指滚动它,所有事件都将被本机视图使用...我最终放弃了此类,所有与之相关的嵌套滚动功能f ** king WebView


I have an app with two activities extends same abstract - one with WebView, another with ViewPager with TabLayout and RecyclerViews - and on this level I've implemented "shared" showing/hiding toolbar with scroll/fling feature:

app:layout_scrollFlags="scroll|snap|enterAlways"

WebView is "pushing out" Toolbar just like RecyclerView under any tab in ViewPager (keeping TabLayout visible), in both cases any scroll to top brings back Toolbar. Just like on this example:

Now I've to change this behavior - Toolbar should be hidden when user is NOT on top of page, so: any scroll to bottom should hide Toolbar if present and only (scroll to top && getScrollY()<=0) should make Toolbar enter:

I've borrowed GIFs from THIS guide, which is suggesting (all other too) that my desired behavior is default with just scroll flag (and possibly snap). So I've removed enterAlways and my "native" Activity started to work as intended out-of-the-box. WebView - no change at all...

I'm suspecting that this bug is caused by my NestedWebView, I'm currently using THIS one. So I've tried to drop-in replace this class with these:

marshi - not scrollable webview content (toolbar shows/hides)

takahirom - toolbar enters even when 1px scroll in up direction almost immediate (kind of snap behavior, but during touch)

hanks - no nested scrolling (fixed toolbar), also with uncommented setNestedScrollingEnabled(true); line

How to achieve second GIF Toolbar behavior cooperating with WebView?

解决方案

read edit on bottom and other answers/comments! don't use this class, also avoid WebView in any form if you can. You will get into trouble sooner or later, maintaining WebView is hell...

I've found few different implementations of NestedWebView and noticed that all of them are old and implements NestedScrollingChild when currently we have 3rd version of this interface: NestedScrollingChild3 (androidx). So I've penetrated current version of NestedSrcollView and I've added some new methods handling, and now it works. To be exact - I was trying to achieve smooth scroll through whole screen just like when WebView would fill whole screen and Toolbar was part of web content (always on top). Also snap is working. use wisely

import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
import android.webkit.WebView;
import android.widget.OverScroller;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.NestedScrollingChild3;
import androidx.core.view.NestedScrollingChildHelper;
import androidx.core.view.ViewCompat;

/**
 * WebView compatible with CoordinatorLayout by snachmsm
 * The implementation based on NestedScrollView of design library androidx v1.0.1
 */
public class NestedWebView extends WebView implements NestedScrollingChild3 {

    private static final String TAG = "NestedWebView";
    private static final int INVALID_POINTER = -1;

    private final int[] mScrollOffset = new int[2];
    private final int[] mScrollConsumed = new int[2];

    private int mLastMotionY;
    private NestedScrollingChildHelper mChildHelper;
    private boolean mIsBeingDragged = false;
    private VelocityTracker mVelocityTracker;
    private int mTouchSlop;
    private int mActivePointerId = INVALID_POINTER;
    private int mNestedYOffset;
    private OverScroller mScroller;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private int mLastScrollerY;

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

    public NestedWebView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.webViewStyle);
    }

    public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setOverScrollMode(WebView.OVER_SCROLL_NEVER);
        initScrollView();
        mChildHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
    }

    private void initScrollView() {
        mScroller = new OverScroller(getContext());
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        final int action = ev.getAction();
        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { // most common
            return true;
        }

        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_MOVE:
                final int activePointerId = mActivePointerId;
                if (activePointerId == INVALID_POINTER) {
                    break;
                }

                final int pointerIndex = ev.findPointerIndex(activePointerId);
                if (pointerIndex == -1) {
                    Log.e(TAG, "Invalid pointerId=" + activePointerId
                            + " in onInterceptTouchEvent");
                    break;
                }

                final int y = (int) ev.getY(pointerIndex);
                final int yDiff = Math.abs(y - mLastMotionY);
                if (yDiff > mTouchSlop
                        && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) {
                    mIsBeingDragged = true;
                    mLastMotionY = y;
                    initVelocityTrackerIfNotExists();
                    mVelocityTracker.addMovement(ev);
                    mNestedYOffset = 0;
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
            case MotionEvent.ACTION_DOWN:
                mLastMotionY = (int) ev.getY();
                mActivePointerId = ev.getPointerId(0);

                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);

                mScroller.computeScrollOffset();
                mIsBeingDragged = !mScroller.isFinished();

                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                recycleVelocityTracker();
                if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
                stopNestedScroll();
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;
        }

        return mIsBeingDragged;
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        initVelocityTrackerIfNotExists();

        MotionEvent vtev = MotionEvent.obtain(ev);

        final int actionMasked = ev.getActionMasked();

        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
        }
        vtev.offsetLocation(0, mNestedYOffset);

        switch (actionMasked) {
            case MotionEvent.ACTION_DOWN:
                if ((mIsBeingDragged = !mScroller.isFinished())) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }

                if (!mScroller.isFinished()) {
                    abortAnimatedScroll();
                }

                mLastMotionY = (int) ev.getY();
                mActivePointerId = ev.getPointerId(0);
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
                break;
            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,
                        ViewCompat.TYPE_TOUCH)) {
                    deltaY -= mScrollConsumed[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) {
                    mLastMotionY = y - mScrollOffset[1];

                    final int oldY = getScrollY();
                    final int range = getScrollRange();

                    // Calling overScrollByCompat will call onOverScrolled, which
                    // calls onScrollChanged if applicable.
                    if (overScrollByCompat(0, deltaY, 0, oldY, 0, range, 0,
                            0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {
                        mVelocityTracker.clear();
                    }

                    final int scrolledDeltaY = getScrollY() - oldY;
                    final int unconsumedY = deltaY - scrolledDeltaY;

                    mScrollConsumed[1] = 0;

                    dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
                            ViewCompat.TYPE_TOUCH, mScrollConsumed);

                    mLastMotionY -= mScrollOffset[1];
                    mNestedYOffset += mScrollOffset[1];
                }
                break;
            case MotionEvent.ACTION_UP:
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
                if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                    if (!dispatchNestedPreFling(0, -initialVelocity)) {
                        dispatchNestedFling(0, -initialVelocity, true);
                        fling(-initialVelocity);
                    }
                } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                        getScrollRange())) {
                    ViewCompat.postInvalidateOnAnimation(this);
                }
                mActivePointerId = INVALID_POINTER;
                endDrag();
                break;
            case MotionEvent.ACTION_CANCEL:
                if (mIsBeingDragged) {
                    if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
                            getScrollRange())) {
                        ViewCompat.postInvalidateOnAnimation(this);
                    }
                }
                mActivePointerId = INVALID_POINTER;
                endDrag();
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                final int index = ev.getActionIndex();
                mLastMotionY = (int) ev.getY(index);
                mActivePointerId = ev.getPointerId(index);
                break;
            case MotionEvent.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
                break;
        }

        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(vtev);
        }
        vtev.recycle();
        return super.onTouchEvent(ev);
    }

    private void abortAnimatedScroll() {
        mScroller.abortAnimation();
        stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
    }

    private void endDrag() {
        mIsBeingDragged = false;

        recycleVelocityTracker();
        stopNestedScroll();
    }

    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
                >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
        final int pointerId = ev.getPointerId(pointerIndex);
        if (pointerId == mActivePointerId) {
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mLastMotionY = (int) ev.getY(newPointerIndex);
            mActivePointerId = ev.getPointerId(newPointerIndex);
            if (mVelocityTracker != null) {
                mVelocityTracker.clear();
            }
        }
    }

    private void fling(int velocityY) {
        int height = getHeight();
        mScroller.fling(getScrollX(), getScrollY(), // start
                0, velocityY, // velocities
                0, 0, // x
                Integer.MIN_VALUE, Integer.MAX_VALUE, // y
                0, height / 2);
        runAnimatedScroll(true);
    }

    private void runAnimatedScroll(boolean participateInNestedScrolling) {
        if (participateInNestedScrolling) {
            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
        } else {
            stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
        }
        mLastScrollerY = getScrollY();
        ViewCompat.postInvalidateOnAnimation(this);
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        if (disallowIntercept) {
            recycleVelocityTracker();
        }
        super.requestDisallowInterceptTouchEvent(disallowIntercept);
    }

    private void initOrResetVelocityTracker() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        } else {
            mVelocityTracker.clear();
        }
    }

    private void initVelocityTrackerIfNotExists() {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
    }

    private void recycleVelocityTracker() {
        if (mVelocityTracker != null) {
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    @Override
    protected boolean overScrollBy(int deltaX, int deltaY,
                                   int scrollX, int scrollY,
                                   int scrollRangeX, int scrollRangeY,
                                   int maxOverScrollX, int maxOverScrollY,
                                   boolean isTouchEvent) {
        // this is causing double scroll call (doubled speed), but this WebView isn't overscrollable
        // all overscrolls are passed to appbar, so commenting this out during drag
        if (!mIsBeingDragged)
            overScrollByCompat(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
                    maxOverScrollX, maxOverScrollY, isTouchEvent);
        // without this call webview won't scroll to top when url change or when user pick input
        // (webview should move a bit making input still in viewport when "adjustResize")
        return true;
    }

    int getScrollRange() {
        //Using scroll range of webview instead of childs as NestedScrollView does.
        return computeVerticalScrollRange();
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return mChildHelper.isNestedScrollingEnabled();
    }

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        mChildHelper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean startNestedScroll(int axes, int type) {
        return mChildHelper.startNestedScroll(axes, type);
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return startNestedScroll(axes, ViewCompat.TYPE_TOUCH);
    }

    @Override
    public void stopNestedScroll(int type) {
        mChildHelper.stopNestedScroll(type);
    }

    @Override
    public void stopNestedScroll() {
        stopNestedScroll(ViewCompat.TYPE_TOUCH);
    }

    @Override
    public boolean hasNestedScrollingParent(int type) {
        return mChildHelper.hasNestedScrollingParent(type);
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return hasNestedScrollingParent(ViewCompat.TYPE_TOUCH);
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                                        int[] offsetInWindow) {
        return dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow, ViewCompat.TYPE_TOUCH);
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                                        int[] offsetInWindow, int type) {
        return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow, type);
    }

    @Override
    public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                                     @Nullable int[] offsetInWindow, int type, @NonNull int[] consumed) {
        mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow, type, consumed);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, ViewCompat.TYPE_TOUCH);
    }


    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
        return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return mChildHelper.dispatchNestedFling(velocityX, velocityY, false);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public int getNestedScrollAxes() {
        return ViewCompat.SCROLL_AXIS_VERTICAL;
    }

    @Override
    public void computeScroll() {
        if (mScroller.isFinished()) {
            return;
        }

        mScroller.computeScrollOffset();
        final int y = mScroller.getCurrY();
        int unconsumed = y - mLastScrollerY;
        mLastScrollerY = y;

        // Nested Scrolling Pre Pass
        mScrollConsumed[1] = 0;
        dispatchNestedPreScroll(0, unconsumed, mScrollConsumed, null,
                ViewCompat.TYPE_NON_TOUCH);
        unconsumed -= mScrollConsumed[1];


        if (unconsumed != 0) {
            // Internal Scroll
            final int oldScrollY = getScrollY();
            overScrollByCompat(0, unconsumed, getScrollX(), oldScrollY, 0, getScrollRange(),
                    0, 0, false);
            final int scrolledByMe = getScrollY() - oldScrollY;
            unconsumed -= scrolledByMe;

            // Nested Scrolling Post Pass
            mScrollConsumed[1] = 0;
            dispatchNestedScroll(0, 0, 0, unconsumed, mScrollOffset,
                    ViewCompat.TYPE_NON_TOUCH, mScrollConsumed);
            unconsumed -= mScrollConsumed[1];
        }

        if (unconsumed != 0) {
            abortAnimatedScroll();
        }

        if (!mScroller.isFinished()) {
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

    // copied from NestedScrollView exacly as it looks, leaving overscroll related code, maybe future use
    private boolean overScrollByCompat(int deltaX, int deltaY,
                                       int scrollX, int scrollY,
                                       int scrollRangeX, int scrollRangeY,
                                       int maxOverScrollX, int maxOverScrollY,
                                       boolean isTouchEvent) {
        final int overScrollMode = getOverScrollMode();
        final boolean canScrollHorizontal =
                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
        final boolean canScrollVertical =
                computeVerticalScrollRange() > computeVerticalScrollExtent();
        final boolean overScrollHorizontal = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
        final boolean overScrollVertical = overScrollMode == View.OVER_SCROLL_ALWAYS
                || (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);

        int newScrollX = scrollX + deltaX;
        if (!overScrollHorizontal) {
            maxOverScrollX = 0;
        }

        int newScrollY = scrollY + deltaY;
        if (!overScrollVertical) {
            maxOverScrollY = 0;
        }

        // Clamp values if at the limits and record
        final int left = -maxOverScrollX;
        final int right = maxOverScrollX + scrollRangeX;
        final int top = -maxOverScrollY;
        final int bottom = maxOverScrollY + scrollRangeY;

        boolean clampedX = false;
        if (newScrollX > right) {
            newScrollX = right;
            clampedX = true;
        } else if (newScrollX < left) {
            newScrollX = left;
            clampedX = true;
        }

        boolean clampedY = false;
        if (newScrollY > bottom) {
            newScrollY = bottom;
            clampedY = true;
        } else if (newScrollY < top) {
            newScrollY = top;
            clampedY = true;
        }

        if (clampedY && !hasNestedScrollingParent(ViewCompat.TYPE_NON_TOUCH)) {
            mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
        }

        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);

        return clampedX || clampedY;
    }
}

edit: this class have a huge flaw! this View consumes all scroll events dispatching them to Toolbar, Coordinator or it own BUT never ever for nested-scrolling div/view inside HTML content. e.g. if you have own implementation of drop down list (or any self-scrolling view) it wont be scrolled by finger, all events consumed by native views... I've ended up throwing this class away and all nested-scrolling features related to f**king WebView

这篇关于NestedWebView可与ScrollingViewBehavior一起正常使用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆