Home | History | Annotate | Download | only in android_webview
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.android_webview;
      6 
      7 import android.content.Context;
      8 import android.graphics.Canvas;
      9 import android.view.View;
     10 import android.widget.EdgeEffect;
     11 
     12 /**
     13  * This class manages the edge glow effect when a WebView is flung or pulled beyond the edges.
     14  */
     15 class OverScrollGlow {
     16     private View mHostView;
     17 
     18     private EdgeEffect mEdgeGlowTop;
     19     private EdgeEffect mEdgeGlowBottom;
     20     private EdgeEffect mEdgeGlowLeft;
     21     private EdgeEffect mEdgeGlowRight;
     22 
     23     private int mOverScrollDeltaX;
     24     private int mOverScrollDeltaY;
     25 
     26     private boolean mShouldPull;
     27 
     28     public OverScrollGlow(Context context, View host) {
     29         mHostView = host;
     30         mEdgeGlowTop = new EdgeEffect(context);
     31         mEdgeGlowBottom = new EdgeEffect(context);
     32         mEdgeGlowLeft = new EdgeEffect(context);
     33         mEdgeGlowRight = new EdgeEffect(context);
     34     }
     35 
     36     public void setShouldPull(boolean shouldPull) {
     37         mShouldPull = shouldPull;
     38     }
     39 
     40     /**
     41      * Pull leftover touch scroll distance into one of the edge glows as appropriate.
     42      *
     43      * @param x Current X scroll offset
     44      * @param y Current Y scroll offset
     45      * @param oldX Old X scroll offset
     46      * @param oldY Old Y scroll offset
     47      * @param maxX Maximum range for horizontal scrolling
     48      * @param maxY Maximum range for vertical scrolling
     49      */
     50     public void pullGlow(int x, int y, int oldX, int oldY, int maxX, int maxY) {
     51         if (!mShouldPull) return;
     52         // Only show overscroll bars if there was no movement in any direction
     53         // as a result of scrolling.
     54         if (oldX == mHostView.getScrollX() && oldY == mHostView.getScrollY()) {
     55             // Don't show left/right glows if we fit the whole content.
     56             // Also don't show if there was vertical movement.
     57             if (maxX > 0) {
     58                 final int pulledToX = oldX + mOverScrollDeltaX;
     59                 if (pulledToX < 0) {
     60                     mEdgeGlowLeft.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
     61                     if (!mEdgeGlowRight.isFinished()) {
     62                         mEdgeGlowRight.onRelease();
     63                     }
     64                 } else if (pulledToX > maxX) {
     65                     mEdgeGlowRight.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
     66                     if (!mEdgeGlowLeft.isFinished()) {
     67                         mEdgeGlowLeft.onRelease();
     68                     }
     69                 }
     70                 mOverScrollDeltaX = 0;
     71             }
     72 
     73             if (maxY > 0 || mHostView.getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
     74                 final int pulledToY = oldY + mOverScrollDeltaY;
     75                 if (pulledToY < 0) {
     76                     mEdgeGlowTop.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
     77                     if (!mEdgeGlowBottom.isFinished()) {
     78                         mEdgeGlowBottom.onRelease();
     79                     }
     80                 } else if (pulledToY > maxY) {
     81                     mEdgeGlowBottom.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
     82                     if (!mEdgeGlowTop.isFinished()) {
     83                         mEdgeGlowTop.onRelease();
     84                     }
     85                 }
     86                 mOverScrollDeltaY = 0;
     87             }
     88         }
     89     }
     90 
     91     /**
     92      * Absorb leftover fling velocity into one of the edge glows as appropriate.
     93      *
     94      * @param x Current X scroll offset
     95      * @param y Current Y scroll offset
     96      * @param oldX Old X scroll offset
     97      * @param oldY Old Y scroll offset
     98      * @param rangeX Maximum range for horizontal scrolling
     99      * @param rangeY Maximum range for vertical scrolling
    100      * @param currentFlingVelocity Current fling velocity
    101      */
    102     public void absorbGlow(int x, int y, int oldX, int oldY, int rangeX, int rangeY,
    103             float currentFlingVelocity) {
    104         if (rangeY > 0 || mHostView.getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
    105             if (y < 0 && oldY >= 0) {
    106                 mEdgeGlowTop.onAbsorb((int) currentFlingVelocity);
    107                 if (!mEdgeGlowBottom.isFinished()) {
    108                     mEdgeGlowBottom.onRelease();
    109                 }
    110             } else if (y > rangeY && oldY <= rangeY) {
    111                 mEdgeGlowBottom.onAbsorb((int) currentFlingVelocity);
    112                 if (!mEdgeGlowTop.isFinished()) {
    113                     mEdgeGlowTop.onRelease();
    114                 }
    115             }
    116         }
    117 
    118         if (rangeX > 0) {
    119             if (x < 0 && oldX >= 0) {
    120                 mEdgeGlowLeft.onAbsorb((int) currentFlingVelocity);
    121                 if (!mEdgeGlowRight.isFinished()) {
    122                     mEdgeGlowRight.onRelease();
    123                 }
    124             } else if (x > rangeX && oldX <= rangeX) {
    125                 mEdgeGlowRight.onAbsorb((int) currentFlingVelocity);
    126                 if (!mEdgeGlowLeft.isFinished()) {
    127                     mEdgeGlowLeft.onRelease();
    128                 }
    129             }
    130         }
    131     }
    132 
    133     /**
    134      * Set touch delta values indicating the current amount of overscroll.
    135      *
    136      * @param deltaX
    137      * @param deltaY
    138      */
    139     public void setOverScrollDeltas(int deltaX, int deltaY) {
    140         mOverScrollDeltaX += deltaX;
    141         mOverScrollDeltaY += deltaY;
    142     }
    143 
    144     /**
    145      * Draw the glow effect along the sides of the widget.
    146      *
    147      * @param canvas Canvas to draw into, transformed into view coordinates.
    148      * @param maxScrollX maximum horizontal scroll offset
    149      * @param maxScrollY maximum vertical scroll offset
    150      * @return true if glow effects are still animating and the view should invalidate again.
    151      */
    152     public boolean drawEdgeGlows(Canvas canvas, int maxScrollX, int maxScrollY) {
    153         final int scrollX = mHostView.getScrollX();
    154         final int scrollY = mHostView.getScrollY();
    155         final int width = mHostView.getWidth();
    156         int height = mHostView.getHeight();
    157 
    158         boolean invalidateForGlow = false;
    159         if (!mEdgeGlowTop.isFinished()) {
    160             final int restoreCount = canvas.save();
    161 
    162             canvas.translate(scrollX, Math.min(0, scrollY));
    163             mEdgeGlowTop.setSize(width, height);
    164             invalidateForGlow |= mEdgeGlowTop.draw(canvas);
    165             canvas.restoreToCount(restoreCount);
    166         }
    167         if (!mEdgeGlowBottom.isFinished()) {
    168             final int restoreCount = canvas.save();
    169 
    170             canvas.translate(-width + scrollX, Math.max(maxScrollY, scrollY) + height);
    171             canvas.rotate(180, width, 0);
    172             mEdgeGlowBottom.setSize(width, height);
    173             invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
    174             canvas.restoreToCount(restoreCount);
    175         }
    176         if (!mEdgeGlowLeft.isFinished()) {
    177             final int restoreCount = canvas.save();
    178 
    179             canvas.rotate(270);
    180             canvas.translate(-height - scrollY, Math.min(0, scrollX));
    181             mEdgeGlowLeft.setSize(height, width);
    182             invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
    183             canvas.restoreToCount(restoreCount);
    184         }
    185         if (!mEdgeGlowRight.isFinished()) {
    186             final int restoreCount = canvas.save();
    187 
    188             canvas.rotate(90);
    189             canvas.translate(scrollY, -(Math.max(scrollX, maxScrollX) + width));
    190             mEdgeGlowRight.setSize(height, width);
    191             invalidateForGlow |= mEdgeGlowRight.draw(canvas);
    192             canvas.restoreToCount(restoreCount);
    193         }
    194         return invalidateForGlow;
    195     }
    196 
    197     /**
    198      * @return True if any glow is still animating
    199      */
    200     public boolean isAnimating() {
    201         return (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() ||
    202                 !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished());
    203     }
    204 
    205     /**
    206      * Release all glows from any touch pulls in progress.
    207      */
    208     public void releaseAll() {
    209         mEdgeGlowTop.onRelease();
    210         mEdgeGlowBottom.onRelease();
    211         mEdgeGlowLeft.onRelease();
    212         mEdgeGlowRight.onRelease();
    213     }
    214 }
    215