com.douban.rexxar.view.NestedWebView.java Source code

Java tutorial

Introduction

Here is the source code for com.douban.rexxar.view.NestedWebView.java

Source

package com.douban.rexxar.view;

import android.annotation.TargetApi;
import android.content.Context;
import android.support.annotation.Keep;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.OverScroller;

/*
 * Copyright (C) 2015 takahirom
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
public class NestedWebView extends WebView implements NestedScrollingChild {

    static final String TAG = "NestedWebView";
    private int mLastX;
    private int mLastY;
    private int mFrozenX;
    private final int[] mOffsetInWindow = new int[2];
    private final int[] mScrollConsumed = new int[2];
    private int mNestedOffsetY;
    private NestedScrollingChildHelper mChildHelper;
    private int mTouchSlop;
    private boolean mNestedScrollEstablish = false;

    // ??
    private boolean mOptimizeHorizontalScroll = false;
    // ??
    private boolean mScrollHorizontalEstablish = false;
    // ??
    private boolean mScrollVerticalEstablish = false;
    private float mLastYWebViewConsume;

    // ?
    private boolean mEnableNestedScroll = true;

    /**
     * Determines speed during touch scrolling
     */
    private VelocityTracker mVelocityTracker;
    private int mMinimumVelocity;
    private int mMaximumVelocity;
    private OverScroller mScroller;
    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);
        mChildHelper = new NestedScrollingChildHelper(this);
        setNestedScrollingEnabled(true);
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();

        WebSettings webSettings = getSettings();
        webSettings.setJavaScriptEnabled(true);
        // 
        addJavascriptInterface(new NestScrollHelper(), "Android_NestScrollHelper");
        // ?
        setOverScrollMode(OVER_SCROLL_NEVER);
        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
        mScroller = new OverScroller(getContext());
    }

    /**
     * ?/? 
     */
    public void enableNestedScroll(boolean enable) {
        mEnableNestedScroll = enable;
    }

    @TargetApi(21)
    public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

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

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

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (!mEnableNestedScroll) {
            return super.onTouchEvent(ev);
        }
        boolean returnValue = false;

        MotionEvent event = MotionEvent.obtain(ev);
        final int action = MotionEventCompat.getActionMasked(event);
        if (action == MotionEvent.ACTION_DOWN) {
            mNestedOffsetY = 0;
        }
        int eventX = (int) event.getX();
        int eventY = (int) event.getY();
        event.offsetLocation(0, -mNestedOffsetY);
        switch (action) {
        case MotionEvent.ACTION_MOVE:
            if (mNestedScrollEstablish) {
                mVelocityTracker.addMovement(ev);
                int deltaX = mLastX - eventX;
                int deltaY = mLastY - eventY;
                // ???
                if (mOptimizeHorizontalScroll) {
                    // ??
                    if (!mScrollHorizontalEstablish && !mScrollVerticalEstablish) {
                        if (Math.abs(deltaX) > Math.abs(deltaY) * 1.5 && Math.abs(deltaX) > mTouchSlop) {
                            mScrollHorizontalEstablish = true;
                        } else if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > mTouchSlop) {
                            mScrollVerticalEstablish = true;
                            mFrozenX = eventX;
                        }
                    }
                }
                mLastX = eventX;
                if (mScrollHorizontalEstablish) {
                    event.offsetLocation(0, deltaY);
                    // ?
                    returnValue = super.onTouchEvent(event);
                } else {
                    // ?
                    if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mOffsetInWindow)) {
                        deltaY -= mScrollConsumed[1];
                        mLastY = eventY - mOffsetInWindow[1];
                        mNestedOffsetY += mOffsetInWindow[1];
                        event.offsetLocation(0, -mOffsetInWindow[1]);
                    } else {
                        mLastY = eventY;
                    }

                    // parent?consumedelta?webView?
                    int oldScrollY = getScrollY();
                    if ((deltaY < 0 && getScrollY() > 0) || deltaY > 0) {
                        // ???
                        if (mScrollVerticalEstablish) {
                            event.offsetLocation(mFrozenX - eventX, 0);
                            returnValue = super.onTouchEvent(event);
                        } else {
                            returnValue = super.onTouchEvent(event);
                        }
                        mLastYWebViewConsume = event.getY();
                    } else {
                        // FIXME ??
                        if (mScrollVerticalEstablish) {
                            event.offsetLocation(mFrozenX - eventX, mLastYWebViewConsume - event.getY());
                        } else {
                            event.offsetLocation(0, mLastYWebViewConsume - event.getY());
                        }
                        super.onTouchEvent(event);
                    }

                    // deltaY
                    if (deltaY == getScrollY() - oldScrollY) {
                        // ???
                    } else if (deltaY < getScrollY() - oldScrollY) {
                        // 
                        if (getScrollY() <= 5) {
                            int dyConsumed = oldScrollY - getScrollY();
                            int dyUnconsumed = deltaY - (getScrollY() - oldScrollY);
                            if (dispatchNestedScroll(0, dyConsumed, 0, dyUnconsumed, mOffsetInWindow)) {
                                mNestedOffsetY += mOffsetInWindow[1];
                                mLastY -= mOffsetInWindow[1];
                                event.offsetLocation(0, mOffsetInWindow[1]);
                            }
                        }
                        returnValue = true;
                    } else {
                        // ???
                    }
                }
            } else {
                returnValue = super.onTouchEvent(event);
            }
            break;
        case MotionEvent.ACTION_DOWN:
            mLastYWebViewConsume = event.getY();
            returnValue = super.onTouchEvent(event);
            mLastX = eventX;
            mLastY = eventY;
            // start NestedScroll
            mNestedScrollEstablish = startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
            mScrollHorizontalEstablish = false;
            mScrollVerticalEstablish = false;
            initOrResetVelocityTracker();
            mVelocityTracker.addMovement(ev);
            break;
        case MotionEvent.ACTION_CANCEL:
            if (mNestedScrollEstablish) {
                returnValue = super.onTouchEvent(event);
                // end NestedScroll
                stopNestedScroll();
            } else {
                returnValue = super.onTouchEvent(event);
            }
            mScrollHorizontalEstablish = false;
            mScrollVerticalEstablish = false;
            mFrozenX = 0;
            recycleVelocityTracker();
            break;
        case MotionEvent.ACTION_UP:
            if (mNestedScrollEstablish) {
                if (mScrollHorizontalEstablish) {
                    // ?
                    event.offsetLocation(0, mLastY - eventY);
                }
                returnValue = super.onTouchEvent(event);
                final VelocityTracker velocityTracker = mVelocityTracker;
                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int initialVelocity = (int) velocityTracker.getYVelocity();
                if ((Math.abs(initialVelocity) > mMinimumVelocity) && getScrollY() == 0) {
                    flingWithNestedDispatch(-initialVelocity);
                } else {// end NestedScroll
                    stopNestedScroll();
                }
            } else {
                returnValue = super.onTouchEvent(event);
            }
            mScrollHorizontalEstablish = false;
            mScrollVerticalEstablish = false;
            mFrozenX = 0;
            recycleVelocityTracker();
            break;
        }
        return returnValue;
    }

    private void flingWithNestedDispatch(int velocityY) {
        if (!dispatchNestedPreFling(0, velocityY)) {
            dispatchNestedFling(0, velocityY, false);
            fling(velocityY);
        }
    }

    /**
     * Fling the scroll view
     *
     * @param velocityY The initial velocity in the Y direction. Positive
     *                  numbers mean that the finger/cursor is moving down the screen,
     *                  which means we want to scroll towards the top.
     */
    public void fling(int velocityY) {
        mLastScrollerY = 0;
        flinging = true;
        startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
        mScroller.fling(0, 0, // start
                0, velocityY, // velocities
                0, 0, // x
                Integer.MIN_VALUE, Integer.MAX_VALUE, // y
                0, 0); // overscroll
        ViewCompat.postInvalidateOnAnimation(getRootView());
    }

    private boolean flinging = false;

    @Override
    public void computeScroll() {
        if (flinging) {
            if (mScroller.computeScrollOffset()) {
                final int y = mScroller.getCurrY();
                int dy = y - mLastScrollerY;

                final int[] scrollConsumedTemp = new int[2];
                // Dispatch up to parent
                if (dispatchNestedPreScroll(0, dy, scrollConsumedTemp, null)) {
                    dy -= scrollConsumedTemp[1];
                }

                if (dy != 0) {
                    dispatchNestedScroll(0, 0, 0, dy, null);
                }

                // Finally update the scroll positions and post an invalidation
                mLastScrollerY = y;
                ViewCompat.postInvalidateOnAnimation(this);
            } else {
                flinging = false;
                // and reset the scroller y
                mLastScrollerY = 0;
                // We can't scroll any more, so stop any indirect scrolling
                if (hasNestedScrollingParent()) {
                    stopNestedScroll();
                }
            }
        } else {
            super.computeScroll();
        }
    }

    // Nested Scroll implements
    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        if (mEnableNestedScroll) {
            mChildHelper.setNestedScrollingEnabled(enabled);
        }
    }

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

    @Override
    public boolean startNestedScroll(int axes) {
        if (mEnableNestedScroll) {
            return mChildHelper.startNestedScroll(axes);
        }
        return false;
    }

    @Override
    public void stopNestedScroll() {
        if (mEnableNestedScroll && !flinging) {
            mChildHelper.stopNestedScroll();
        }
    }

    @Override
    public boolean hasNestedScrollingParent() {
        if (mEnableNestedScroll) {
            return mChildHelper.hasNestedScrollingParent();
        }
        return false;
    }

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

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

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

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

    @Keep
    private class NestScrollHelper {
        @JavascriptInterface
        public void optimizeHorizontalScroll() {
            mOptimizeHorizontalScroll = true;
        }
    }

}