使用NestedWebView 实现WebView 顶部加原生内容嵌套滑动

1.要实现这个WebView 顶部加原生内容嵌套滑动,想到嵌套滑动比较好的解决方案就是"NestedScrollingChild2"和"NestedScrollingParent2"

2.我这里的方案是

CoordinatorLayout+AppBarLayout+NestedWebView

3.布局如下:activity_nest.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        app:layout_behavior="com.ange.demo.nested.NestedWebHeardBehavior"
        android:layout_width="match_parent"
        android:background="#fff"
        app:elevation="0dp"
        android:layout_height="wrap_content">

        <LinearLayout
            app:layout_scrollFlags="scroll"
            android:layout_width="match_parent"
            android:layout_height="300dp"
            >
            <TextView
                android:text="@string/info"
                android:layout_width="match_parent"
                android:layout_height="200dp" />
        </LinearLayout>
    </android.support.design.widget.AppBarLayout>

   
    <com.ange.demo.nested.NestedWebView
        app:layout_behavior="@string/web_behavior"
        android:id="@+id/web"
        android:overScrollMode="never"
        android:scrollbars="none"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </com.ange.demo.nested.NestedWebView>
</android.support.design.widget.CoordinatorLayout>

4.此方案比较重要的是NestedWebView ,NestedWebHeardBehavior,WebBehavior这三个类。

5.NestedWebView

摘取重点部分解读:

(1)setNestedScrollingEnabled(true);//设置支持嵌套滑动

(2)mScrollingChildHelper = new NestedScrollingChildHelper(this);// 官方提供的嵌套滑动辅助类

(3)vtev.offsetLocation(0, mNestedYOffset);//控制webView的触摸事件,指在y轴上滑动时,当事件需要被其他控件消费,改变原来的事件,webView 的onTouchEvent 接受到的事件是我们偏移后的事件。在纵轴上减少去被其他控件消费的部分。

(4)startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH); //开始嵌套滑动,触摸类型的,有一种是非触摸类型的(惯性滑动)

(5)关于偏移量采集mLastMotionY = (int) event.getY();,统一用原始的event 来采集,这样保持计算的y轴的滑动距离准确性。

(6)分配事件消费 ,如 webView 滑动多少距离,Applayoutbar 滑动多少,

dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH) 通知父布局或AppbarLayout需要滑动多少距离,然后把未消费的距离存储在mScrollConsumed,返回布尔值,是否消费事件;剩下未消费的滑动距离让webView处理。

package com.ange.demo.nested;

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild2;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent2;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.EdgeEffectCompat;
import android.support.v4.widget.NestedScrollView;
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.EdgeEffect;
import android.widget.OverScroller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static android.support.v4.view.ViewCompat.TYPE_TOUCH;
import static android.support.v4.widget.ViewDragHelper.INVALID_POINTER;

public class NestedWebView extends WebView implements NestedScrollingChild2 {
    private NestedScrollingChildHelper mScrollingChildHelper;
    private int mLastMotionY;
    /**
     * 用于跟踪触摸事件速度的辅助类,用于实现
     * fling 和其他类似的手势。
     */
    private VelocityTracker mVelocityTracker;
    /**
     * True if the user is currently dragging this ScrollView around. This is
     * not the same as 'is being flinged', which can be checked by
     * mScroller.isFinished() (flinging begins when the user lifts his finger).
     */
    private boolean mIsBeingDragged = false;
    /**
     * ID of the active pointer. This is used to retain consistency during
     * drags/flings if multiple pointers are used.(多点触控有用)
     */
    private int mActivePointerId = INVALID_POINTER;
    private int mTouchSlop;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private final int[] mScrollOffset = new int[2];
    private final int[] mScrollConsumed = new int[2];
    private OverScroller mScroller;
    private int mNestedYOffset;
    private int mLastScrollerY;
    private int mLastY;
    private int moveDistance;
    private static final String TAG = "NestedWebView";

    public NestedWebView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new OverScroller(getContext());
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        setNestedScrollingEnabled(true);//设置支持嵌套滑动
    }

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

    private NestedScrollingChildHelper getScrollingChildHelper() {
        if (mScrollingChildHelper == null) {
            mScrollingChildHelper = new NestedScrollingChildHelper(this);
        }
        return mScrollingChildHelper;
    }

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        initVelocityTrackerIfNotExists();
        boolean eventAddedToVelocityTracker = false;
        MotionEvent vtev = MotionEvent.obtain(event);//复制一个event
        final int actionMasked = event.getActionMasked();//类似getAction
        boolean result = false;
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            mNestedYOffset = 0;
            isFlinging = false;
        }
        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 being flinged and user touches, stop the fling. isFinished
                 * will be false if being flinged.//如果在fling 的过程中用户触摸屏幕,则停止fling
                 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }

                // Remember where the motion event started
                mLastMotionY = (int) event.getY();
                mLastY = mLastMotionY;
                mActivePointerId = event.getPointerId(0);
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
                result = super.onTouchEvent(event);
                break;
            case MotionEvent.ACTION_MOVE:

                final int activePointerIndex = event.findPointerIndex(mActivePointerId);
                if (activePointerIndex == -1) {
                    break;
                }
                final int y = (int) event.getY(activePointerIndex);

                int deltaY = mLastMotionY - y;
                if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) {
                    Log.d(TAG, "dispatchNestedPreScroll消费:" + mScrollConsumed[1]);
                    deltaY -= mScrollConsumed[1];//纵轴位移- 被父布局消费的滑动距离
                    Log.d(TAG, "dispatchNestedPreScroll未消费:" + deltaY);
//                    vtev.offsetLocation(0, mScrollOffset[1]);//这句加不加一样
                }
                moveDistance = deltaY;
                if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    mIsBeingDragged = true;
                }

                if (mIsBeingDragged) {
                    mLastMotionY = y - mScrollOffset[1];//上一次的坐标
                    int scrolledDeltaY = 0;
                    int unconsumedY = deltaY;
                    if (Math.abs(deltaY) > 0) {

                        if (deltaY <= 0) {
                            if (canScrollVertically(-1)) {//向顶部滑动
                                if (getScrollY() + deltaY < 0) {
                                    scrolledDeltaY = -getScrollY();
                                    unconsumedY = getScrollY() + deltaY;
                                    vtev.offsetLocation(0, unconsumedY);//这行不知对不对
                                    mNestedYOffset += unconsumedY;
                                } else {
                                    scrolledDeltaY = deltaY;
                                    unconsumedY = 0;
                                }
                            }
                        } else {
                            if (canScrollVertically(1)) {
                                //todo 这里没有处理底部的事件传递给父布局,本例不需要
                                Log.d(TAG, "canScrollVertically:deltaY:" + deltaY);
                                if (deltaY - getTop() > 0) {
                                    scrolledDeltaY = deltaY - getTop();
                                    unconsumedY = getTop();
                                    vtev.offsetLocation(0, unconsumedY);//这行不知对不对
                                    mNestedYOffset += unconsumedY;
                                } else {
                                    scrolledDeltaY = deltaY;
                                    unconsumedY = 0;
                                }
                            }
                        }
                    }
                    if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset)) {
                        mLastMotionY -= mScrollOffset[1];
                        vtev.offsetLocation(0, mScrollOffset[1]);
                        mNestedYOffset += mScrollOffset[1];
                    }
                }
                if (deltaY == 0 && mIsBeingDragged) {
                    result = true;
                } else {
                    result = super.onTouchEvent(vtev);
                }

                break;
            case MotionEvent.ACTION_UP:
                if (mVelocityTracker != null) {
                    mVelocityTracker.addMovement(vtev);
                }
                eventAddedToVelocityTracker = true;
                caculateV(mActivePointerId, (int) event.getY());
                mLastY = (int) event.getY();
                mActivePointerId = INVALID_POINTER;
                endDrag();
                if (mVelocityTracker != null) {
                    mVelocityTracker.clear();
                }
                stopNestedScroll(TYPE_TOUCH);
                result = super.onTouchEvent(vtev);
                break;
            case MotionEvent.ACTION_CANCEL:
                mActivePointerId = INVALID_POINTER;
                endDrag();
                if (mVelocityTracker != null) {
                    mVelocityTracker.clear();
                }
                stopNestedScroll(TYPE_TOUCH);
                result = super.onTouchEvent(event);
                break;
        }
        if (!eventAddedToVelocityTracker) {
            if (mVelocityTracker != null) {
                mVelocityTracker.addMovement(vtev);
            }
        }
        vtev.recycle();
        return result;
    }

    private boolean isFlinging;

    /**
     * 处理fling 速度问题
     * @param mActivePointerId
     * @param curY
     */
    private void caculateV(int mActivePointerId, int curY) {
        mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
        int initialVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
        Log.d(TAG, " caculateV curY:" + curY + " mLastY:" + mLastY + " initialVelocity:" + initialVelocity);
        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
            mLastScrollerY = getScrollY();
            isFlinging = true;
        }
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (isFlinging) {
            int dy = getScrollY() - mLastScrollerY;
            if (getScrollY() == 0) {
                int velocityY = 1000;
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_NON_TOUCH);
                if (moveDistance < 0) {
                    dispatchNestedScroll(0, dy, 0, -velocityY, null,
                            ViewCompat.TYPE_NON_TOUCH);
                }
                isFlinging = false;
                stopNestedScroll(ViewCompat.TYPE_NON_TOUCH);
            }
        }
        Log.d(TAG, "computeScroll webView:getScrollY:" + getScrollY());
    }

    private void endDrag() {
        mIsBeingDragged = false;
        recycleVelocityTracker();
        stopNestedScroll(ViewCompat.TYPE_TOUCH);
    }

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

    @Override
    public void flingScroll(int vx, int vy) {
        super.flingScroll(vx, vy);
    }

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

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

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

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

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

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

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

    @Override
    public boolean startNestedScroll(int axes) {
        return getScrollingChildHelper().startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        getScrollingChildHelper().stopNestedScroll();
    }

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

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

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

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

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

6.NestedWebHeardBehavior   用于控制AppBarLayout的行为,在webView 已经滑动到顶部,而且由上往下滑动,才让AppBarLayout由上往下拉出来

package com.ange.demo.nested;

import android.content.Context;
import android.support.annotation.NonNull;
import android.support.design.widget.AppBarLayout;
import android.support.design.widget.CoordinatorLayout;
import android.support.v4.widget.NestedScrollView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;

import com.ange.demo.R;

public class NestedWebHeardBehavior extends AppBarLayout.Behavior {
    private static final String TAG = "NestedWebHeardBehavior";

    public NestedWebHeardBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        WebView view=coordinatorLayout.findViewById(R.id.web);
        Log.d(TAG,"dyUnconsumed="+dyUnconsumed);
        Log.d(TAG,"getScrollY="+view.getScrollY());
        if(dyUnconsumed<0&&view.getScrollY()<=0){//由上往下滑动
            super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
        }
    }
}

7.WebBehavior 用于控制NestedWebView的行为 这里没做任何操作

package com.ange.demo.nested;

import android.content.Context;
import android.support.design.widget.AppBarLayout;
import android.util.AttributeSet;


public class WebBehavior extends AppBarLayout.ScrollingViewBehavior {
    private final static String TAG="WebBehavior";
    public WebBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值