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);
}
}