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