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