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 OverScrollGlow(Context context, View host)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 setShouldPull(boolean shouldPull)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 */ pullGlow(int x, int y, int oldX, int oldY, int maxX, int maxY)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 */ absorbGlow(int x, int y, int oldX, int oldY, int rangeX, int rangeY, float currentFlingVelocity)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 */ setOverScrollDeltas(int deltaX, int deltaY)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 */ drawEdgeGlows(Canvas canvas, int maxScrollX, int maxScrollY)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 */ isAnimating()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 */ releaseAll()208 public void releaseAll() { 209 mEdgeGlowTop.onRelease(); 210 mEdgeGlowBottom.onRelease(); 211 mEdgeGlowLeft.onRelease(); 212 mEdgeGlowRight.onRelease(); 213 } 214 } 215