OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chrome.browser.banners; | 5 package org.chromium.chrome.browser.banners; |
6 | 6 |
7 import android.animation.Animator; | 7 import android.animation.Animator; |
8 import android.animation.Animator.AnimatorListener; | 8 import android.animation.Animator.AnimatorListener; |
9 import android.animation.AnimatorListenerAdapter; | 9 import android.animation.AnimatorListenerAdapter; |
10 import android.animation.ObjectAnimator; | 10 import android.animation.ObjectAnimator; |
11 import android.content.Context; | 11 import android.content.Context; |
12 import android.graphics.Region; | 12 import android.graphics.Region; |
13 import android.util.AttributeSet; | 13 import android.util.AttributeSet; |
14 import android.view.Gravity; | 14 import android.view.Gravity; |
15 import android.view.View; | 15 import android.view.View; |
16 import android.view.ViewGroup; | 16 import android.view.ViewGroup; |
17 import android.view.animation.DecelerateInterpolator; | 17 import android.view.animation.DecelerateInterpolator; |
18 import android.view.animation.Interpolator; | 18 import android.view.animation.Interpolator; |
19 import android.widget.FrameLayout; | 19 import android.widget.FrameLayout; |
20 | 20 |
21 import org.chromium.chrome.browser.util.MathUtils; | |
21 import org.chromium.content.browser.ContentViewCore; | 22 import org.chromium.content.browser.ContentViewCore; |
22 import org.chromium.content_public.browser.GestureStateListener; | 23 import org.chromium.content_public.browser.GestureStateListener; |
23 | 24 |
24 /** | 25 /** |
25 * View that slides up from the bottom of the page and slides away as the user s crolls the page. | 26 * View that slides up from the bottom of the page and slides away as the user s crolls the page. |
26 * Meant to be tacked onto the {@link org.chromium.content.browser.ContentViewCo re}'s view and | 27 * Meant to be tacked onto the {@link org.chromium.content.browser.ContentViewCo re}'s view and |
27 * alerted when either the page scroll position or viewport size changes. | 28 * alerted when either the page scroll position or viewport size changes. |
28 * | 29 * |
29 * GENERAL BEHAVIOR | 30 * GENERAL BEHAVIOR |
30 * This View is brought onto the screen by sliding upwards from the bottom of th e screen. Afterward | 31 * This View is brought onto the screen by sliding upwards from the bottom of th e screen. Afterward |
31 * the View slides onto and off of the screen vertically as the user scrolls upw ards or | 32 * the View slides onto and off of the screen vertically as the user scrolls upw ards or |
32 * downwards on the page. | 33 * downwards on the page. |
33 * | 34 * |
34 * VERTICAL SCROLL CALCULATIONS | |
35 * To determine how close the user is to the top of the page, the View must not only be informed of | |
36 * page scroll position changes, but also of changes in the viewport size (which happens as the | |
37 * omnibox appears and disappears, or as the page rotates e.g.). When the viewp ort size gradually | |
38 * shrinks, the user is most likely to be scrolling the page downwards while the omnibox comes back | |
39 * into view. | |
40 * | |
41 * When the user first begins scrolling the page, both the scroll position and t he viewport size are | |
42 * summed and recorded together. This is because a pixel change in the viewport height is | |
43 * equivalent to a pixel change in the content's scroll offset: | |
44 * - As the user scrolls the page downward, either the viewport height will incr ease (as the omnibox | |
45 * is slid off of the screen) or the content scroll offset will increase. | |
46 * - As the user scrolls the page upward, either the viewport height will decrea se (as the omnibox | |
47 * is brought back onto the screen) or the content scroll offset will decrease . | |
48 * | |
49 * As the scroll offset or the viewport height are updated via a scroll or fling , the difference | 35 * As the scroll offset or the viewport height are updated via a scroll or fling , the difference |
50 * from the initial value is used to determine the View's Y-translation. If a g esture is stopped, | 36 * from the initial value is used to determine the View's Y-translation. If a g esture is stopped, |
51 * the View will be snapped back into the center of the screen or entirely off o f the screen, based | 37 * the View will be snapped back into the center of the screen or entirely off o f the screen, based |
52 * on how much of the View is visible, or where the user is currently located on the page. | 38 * on how much of the View is visible, or where the user is currently located on the page. |
53 */ | 39 */ |
54 public abstract class SwipableOverlayView extends FrameLayout { | 40 public abstract class SwipableOverlayView extends FrameLayout { |
55 private static final float FULL_THRESHOLD = 0.5f; | 41 private static final float FULL_THRESHOLD = 0.5f; |
56 private static final float VERTICAL_FLING_SHOW_THRESHOLD = 0.2f; | 42 private static final float VERTICAL_FLING_SHOW_THRESHOLD = 0.2f; |
57 private static final float VERTICAL_FLING_HIDE_THRESHOLD = 0.9f; | 43 private static final float VERTICAL_FLING_HIDE_THRESHOLD = 0.9f; |
58 | 44 |
(...skipping 17 matching lines...) Expand all Loading... | |
76 | 62 |
77 // Tracks whether the user is scrolling or flinging. | 63 // Tracks whether the user is scrolling or flinging. |
78 private int mGestureState; | 64 private int mGestureState; |
79 | 65 |
80 // Animation currently being used to translate the View. | 66 // Animation currently being used to translate the View. |
81 private Animator mCurrentAnimation; | 67 private Animator mCurrentAnimation; |
82 | 68 |
83 // Used to determine when the layout has changed and the Viewport must be up dated. | 69 // Used to determine when the layout has changed and the Viewport must be up dated. |
84 private int mParentHeight; | 70 private int mParentHeight; |
85 | 71 |
86 // Location of the View when the current gesture was first started. | |
87 private float mInitialTranslationY; | |
88 | |
89 // Offset from the top of the page when the current gesture was first starte d. | 72 // Offset from the top of the page when the current gesture was first starte d. |
90 private int mInitialOffsetY; | 73 private int mInitialOffsetY; |
91 | 74 |
92 // How tall the View is, including its margins. | 75 // How tall the View is, including its margins. |
93 private int mTotalHeight; | 76 private int mTotalHeight; |
94 | 77 |
95 // Whether or not the View ever been fully displayed. | 78 // Whether or not the View ever been fully displayed. |
96 private boolean mIsBeingDisplayedForFirstTime; | 79 private boolean mIsBeingDisplayedForFirstTime; |
97 | 80 |
98 // The ContentViewCore to which the overlay is added. | 81 // The ContentViewCore to which the overlay is added. |
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
200 super.onLayout(changed, l, t, r, b); | 183 super.onLayout(changed, l, t, r, b); |
201 } | 184 } |
202 | 185 |
203 /** | 186 /** |
204 * Creates a listener than monitors the ContentViewCore for scrolls and flin gs. | 187 * Creates a listener than monitors the ContentViewCore for scrolls and flin gs. |
205 * The listener updates the location of this View to account for the user's gestures. | 188 * The listener updates the location of this View to account for the user's gestures. |
206 * @return GestureStateListener to send to the ContentViewCore. | 189 * @return GestureStateListener to send to the ContentViewCore. |
207 */ | 190 */ |
208 private GestureStateListener createGestureStateListener() { | 191 private GestureStateListener createGestureStateListener() { |
209 return new GestureStateListener() { | 192 return new GestureStateListener() { |
193 // Tracks the previous event's scroll offset to determine if a scrol l is up or down. | |
gone
2017/05/08 17:01:53
javadoc syntax instead
mdjones
2017/05/08 18:13:06
Done.
| |
194 private int mLastScrollOffsetY; | |
195 | |
196 // Location of the View when the current gesture was first started. | |
197 private float mInitialTranslationY; | |
198 | |
199 // The initial extent of the scroll when triggered. | |
200 private float mInitialExtentY; | |
201 | |
210 @Override | 202 @Override |
211 public void onFlingStartGesture(int scrollOffsetY, int scrollExtentY ) { | 203 public void onFlingStartGesture(int scrollOffsetY, int scrollExtentY ) { |
212 if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return; | 204 if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return; |
213 beginGesture(scrollOffsetY, scrollExtentY); | 205 resetInitialOffsets(scrollOffsetY, scrollExtentY); |
214 mGestureState = GESTURE_FLINGING; | 206 mGestureState = GESTURE_FLINGING; |
215 } | 207 } |
216 | 208 |
217 @Override | 209 @Override |
218 public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) { | 210 public void onFlingEndGesture(int scrollOffsetY, int scrollExtentY) { |
219 if (mGestureState != GESTURE_FLINGING) return; | 211 if (mGestureState != GESTURE_FLINGING) return; |
220 mGestureState = GESTURE_NONE; | 212 mGestureState = GESTURE_NONE; |
221 | 213 |
222 int finalOffsetY = computeScrollDifference(scrollOffsetY, scroll ExtentY); | |
223 updateTranslation(scrollOffsetY, scrollExtentY); | 214 updateTranslation(scrollOffsetY, scrollExtentY); |
224 | 215 |
225 boolean isScrollingDownward = finalOffsetY > 0; | 216 boolean isScrollingDownward = scrollOffsetY > mLastScrollOffsetY ; |
226 | 217 |
227 boolean isVisibleInitially = mInitialTranslationY < mTotalHeight ; | 218 boolean isVisibleInitially = mInitialTranslationY < mTotalHeight ; |
228 float percentageVisible = 1.0f - (getTranslationY() / mTotalHeig ht); | 219 float percentageVisible = 1.0f - (getTranslationY() / mTotalHeig ht); |
229 float visibilityThreshold = isVisibleInitially | 220 float visibilityThreshold = isVisibleInitially |
230 ? VERTICAL_FLING_HIDE_THRESHOLD : VERTICAL_FLING_SHOW_TH RESHOLD; | 221 ? VERTICAL_FLING_HIDE_THRESHOLD : VERTICAL_FLING_SHOW_TH RESHOLD; |
231 boolean isVisibleEnough = percentageVisible > visibilityThreshol d; | 222 boolean isVisibleEnough = percentageVisible > visibilityThreshol d; |
223 boolean isNearTopOfPage = scrollOffsetY < (mTotalHeight * FULL_T HRESHOLD); | |
232 | 224 |
233 boolean show = !isScrollingDownward; | 225 boolean show = (!isScrollingDownward && isVisibleEnough) || isNe arTopOfPage; |
234 if (isVisibleInitially) { | 226 |
235 // Check if the View was moving off-screen. | |
236 boolean isHiding = getTranslationY() > mInitialTranslationY; | |
237 show &= isVisibleEnough || !isHiding; | |
238 } else { | |
239 // When near the top of the page, there's not much room left to scroll. | |
240 boolean isNearTopOfPage = finalOffsetY < (mTotalHeight * FUL L_THRESHOLD); | |
241 show &= isVisibleEnough || isNearTopOfPage; | |
242 } | |
243 createVerticalSnapAnimation(show); | 227 createVerticalSnapAnimation(show); |
244 } | 228 } |
245 | 229 |
246 @Override | 230 @Override |
247 public void onScrollStarted(int scrollOffsetY, int scrollExtentY) { | 231 public void onScrollStarted(int scrollOffsetY, int scrollExtentY) { |
248 if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return; | 232 if (!isAllowedToAutoHide() || !cancelCurrentAnimation()) return; |
249 beginGesture(scrollOffsetY, scrollExtentY); | 233 resetInitialOffsets(scrollOffsetY, scrollExtentY); |
234 mLastScrollOffsetY = scrollOffsetY; | |
250 mGestureState = GESTURE_SCROLLING; | 235 mGestureState = GESTURE_SCROLLING; |
251 } | 236 } |
252 | 237 |
253 @Override | 238 @Override |
254 public void onScrollEnded(int scrollOffsetY, int scrollExtentY) { | 239 public void onScrollEnded(int scrollOffsetY, int scrollExtentY) { |
255 if (mGestureState != GESTURE_SCROLLING) return; | 240 if (mGestureState != GESTURE_SCROLLING) return; |
256 mGestureState = GESTURE_NONE; | 241 mGestureState = GESTURE_NONE; |
257 | 242 |
258 int finalOffsetY = computeScrollDifference(scrollOffsetY, scroll ExtentY); | |
259 updateTranslation(scrollOffsetY, scrollExtentY); | 243 updateTranslation(scrollOffsetY, scrollExtentY); |
260 | 244 |
261 boolean isNearTopOfPage = finalOffsetY < (mTotalHeight * FULL_TH RESHOLD); | 245 boolean isNearTopOfPage = scrollOffsetY < (mTotalHeight * FULL_T HRESHOLD); |
262 boolean isVisibleEnough = getTranslationY() < mTotalHeight * FUL L_THRESHOLD; | 246 boolean isVisibleEnough = getTranslationY() < mTotalHeight * FUL L_THRESHOLD; |
263 createVerticalSnapAnimation(isNearTopOfPage || isVisibleEnough); | 247 createVerticalSnapAnimation(isNearTopOfPage || isVisibleEnough); |
264 } | 248 } |
265 | 249 |
266 @Override | 250 @Override |
267 public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scr ollExtentY) { | 251 public void onScrollOffsetOrExtentChanged(int scrollOffsetY, int scr ollExtentY) { |
268 // This function is called for both fling and scrolls. | 252 // This function is called for both fling and scrolls. |
269 if (mGestureState == GESTURE_NONE || !cancelCurrentAnimation()) return; | 253 if (mGestureState == GESTURE_NONE || !cancelCurrentAnimation()) return; |
254 mLastScrollOffsetY = scrollOffsetY; | |
270 updateTranslation(scrollOffsetY, scrollExtentY); | 255 updateTranslation(scrollOffsetY, scrollExtentY); |
271 } | 256 } |
272 | 257 |
273 private void updateTranslation(int scrollOffsetY, int scrollExtentY) { | 258 private void updateTranslation(int scrollOffsetY, int scrollExtentY) { |
274 float translation = mInitialTranslationY | 259 float scrollDiff = |
275 + computeScrollDifference(scrollOffsetY, scrollExtentY); | 260 (scrollOffsetY - mInitialOffsetY) + (scrollExtentY - mIn itialExtentY); |
276 translation = Math.max(0.0f, Math.min(mTotalHeight, translation) ); | 261 float translation = |
262 MathUtils.clamp(mInitialTranslationY + scrollDiff, mTota lHeight, 0); | |
263 | |
264 // If the container has reached the completely shown position, r eset the initial | |
265 // scroll so any movement will start hiding it again. | |
266 if (translation <= 0f) resetInitialOffsets(scrollOffsetY, scroll ExtentY); | |
267 | |
277 setTranslationY(translation); | 268 setTranslationY(translation); |
278 } | 269 } |
270 | |
271 /** | |
272 * Records the conditions of the page to handle scrolls appropriatel y. | |
273 */ | |
274 private void resetInitialOffsets(int scrollOffsetY, int scrollExtent Y) { | |
275 mInitialOffsetY = scrollOffsetY; | |
276 mInitialExtentY = scrollExtentY; | |
277 mInitialTranslationY = getTranslationY(); | |
278 } | |
279 }; | 279 }; |
280 } | 280 } |
281 | 281 |
282 /** | 282 /** |
283 * Creates a listener that is used only to animate the View coming onto the screen. | 283 * Creates a listener that is used only to animate the View coming onto the screen. |
284 * @return The SimpleOnGestureListener that will monitor the View. | 284 * @return The SimpleOnGestureListener that will monitor the View. |
285 */ | 285 */ |
286 private View.OnLayoutChangeListener createLayoutChangeListener() { | 286 private View.OnLayoutChangeListener createLayoutChangeListener() { |
287 return new View.OnLayoutChangeListener() { | 287 return new View.OnLayoutChangeListener() { |
288 @Override | 288 @Override |
(...skipping 21 matching lines...) Expand all Loading... | |
310 float yDifference = Math.abs(translationY - getTranslationY()) / mTotalH eight; | 310 float yDifference = Math.abs(translationY - getTranslationY()) / mTotalH eight; |
311 long duration = Math.max(0, (long) (ANIMATION_DURATION_MS * yDifference) ); | 311 long duration = Math.max(0, (long) (ANIMATION_DURATION_MS * yDifference) ); |
312 | 312 |
313 mCurrentAnimation = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, tra nslationY); | 313 mCurrentAnimation = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, tra nslationY); |
314 mCurrentAnimation.setDuration(duration); | 314 mCurrentAnimation.setDuration(duration); |
315 mCurrentAnimation.addListener(mAnimatorListener); | 315 mCurrentAnimation.addListener(mAnimatorListener); |
316 mCurrentAnimation.setInterpolator(mInterpolator); | 316 mCurrentAnimation.setInterpolator(mInterpolator); |
317 mCurrentAnimation.start(); | 317 mCurrentAnimation.start(); |
318 } | 318 } |
319 | 319 |
320 private int computeScrollDifference(int scrollOffsetY, int scrollExtentY) { | |
321 return scrollOffsetY + scrollExtentY - mInitialOffsetY; | |
322 } | |
323 | |
324 /** | 320 /** |
325 * Creates an AnimatorListenerAdapter that cleans up after an animation is c ompleted. | 321 * Creates an AnimatorListenerAdapter that cleans up after an animation is c ompleted. |
326 * @return {@link AnimatorListenerAdapter} to use for animations. | 322 * @return {@link AnimatorListenerAdapter} to use for animations. |
327 */ | 323 */ |
328 private AnimatorListener createAnimatorListener() { | 324 private AnimatorListener createAnimatorListener() { |
329 return new AnimatorListenerAdapter() { | 325 return new AnimatorListenerAdapter() { |
330 @Override | 326 @Override |
331 public void onAnimationEnd(Animator animation) { | 327 public void onAnimationEnd(Animator animation) { |
332 mGestureState = GESTURE_NONE; | 328 mGestureState = GESTURE_NONE; |
333 mCurrentAnimation = null; | 329 mCurrentAnimation = null; |
334 mIsBeingDisplayedForFirstTime = false; | 330 mIsBeingDisplayedForFirstTime = false; |
335 } | 331 } |
336 }; | 332 }; |
337 } | 333 } |
338 | 334 |
339 /** | 335 /** |
340 * Records the conditions of the page when a gesture is initiated. | |
341 */ | |
342 private void beginGesture(int scrollOffsetY, int scrollExtentY) { | |
343 mInitialTranslationY = getTranslationY(); | |
344 boolean isInitiallyVisible = mInitialTranslationY < mTotalHeight; | |
345 int startingY = isInitiallyVisible ? scrollOffsetY : Math.min(scrollOffs etY, mTotalHeight); | |
346 mInitialOffsetY = startingY + scrollExtentY; | |
347 } | |
348 | |
349 /** | |
350 * Cancels the current animation, unless the View is coming onto the screen for the first time. | 336 * Cancels the current animation, unless the View is coming onto the screen for the first time. |
351 * @return True if the animation was canceled or wasn't running, false other wise. | 337 * @return True if the animation was canceled or wasn't running, false other wise. |
352 */ | 338 */ |
353 private boolean cancelCurrentAnimation() { | 339 private boolean cancelCurrentAnimation() { |
354 if (mIsBeingDisplayedForFirstTime) return false; | 340 if (mIsBeingDisplayedForFirstTime) return false; |
355 if (mCurrentAnimation != null) mCurrentAnimation.cancel(); | 341 if (mCurrentAnimation != null) mCurrentAnimation.cancel(); |
356 return true; | 342 return true; |
357 } | 343 } |
358 | 344 |
359 /** | 345 /** |
(...skipping 13 matching lines...) Expand all Loading... | |
373 public boolean gatherTransparentRegion(Region region) { | 359 public boolean gatherTransparentRegion(Region region) { |
374 float translationY = getTranslationY(); | 360 float translationY = getTranslationY(); |
375 setTranslationY(0); | 361 setTranslationY(0); |
376 boolean result = super.gatherTransparentRegion(region); | 362 boolean result = super.gatherTransparentRegion(region); |
377 // Restoring TranslationY invalidates this view unnecessarily. However, this function | 363 // Restoring TranslationY invalidates this view unnecessarily. However, this function |
378 // is called as part of layout, which implies a full redraw is about to occur anyway. | 364 // is called as part of layout, which implies a full redraw is about to occur anyway. |
379 setTranslationY(translationY); | 365 setTranslationY(translationY); |
380 return result; | 366 return result; |
381 } | 367 } |
382 } | 368 } |
OLD | NEW |