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