Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Riddle Hsu | 0c37598 | 2018-06-21 22:06:43 +0800 | [diff] [blame] | 17 | package com.android.documentsui; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 18 | |
| 19 | import android.graphics.Point; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 20 | |
| 21 | /** |
| 22 | * Provides auto-scrolling upon request when user's interaction with the application |
Riddle Hsu | 0c37598 | 2018-06-21 22:06:43 +0800 | [diff] [blame] | 23 | * introduces a natural intent to scroll. Used by DragHoverListener to allow auto scrolling |
| 24 | * when user either does band selection, attempting to drag and drop files to somewhere off |
| 25 | * the current screen, or trying to motion select past top/bottom of the screen. |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 26 | */ |
| 27 | public final class ViewAutoScroller implements Runnable { |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 28 | |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 29 | // ratio used to calculate the top/bottom hotspot region; used with view height |
| 30 | public static final float TOP_BOTTOM_THRESHOLD_RATIO = 0.125f; |
| 31 | public static final int MAX_SCROLL_STEP = 70; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 32 | |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 33 | private ScrollHost mHost; |
Steve McKay | 5a62037 | 2017-09-11 12:18:56 -0700 | [diff] [blame] | 34 | private ScrollerCallbacks mCallbacks; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 35 | |
Steve McKay | 5a62037 | 2017-09-11 12:18:56 -0700 | [diff] [blame] | 36 | public ViewAutoScroller(ScrollHost scrollHost, ScrollerCallbacks callbacks) { |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 37 | assert scrollHost != null; |
| 38 | assert callbacks != null; |
| 39 | |
| 40 | mHost = scrollHost; |
| 41 | mCallbacks = callbacks; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 42 | } |
| 43 | |
| 44 | /** |
| 45 | * Attempts to smooth-scroll the view at the given UI frame. Application should be |
| 46 | * responsible to do any clean up (such as unsubscribing scrollListeners) after the run has |
| 47 | * finished, and re-run this method on the next UI frame if applicable. |
| 48 | */ |
| 49 | @Override |
| 50 | public void run() { |
| 51 | // Compute the number of pixels the pointer's y-coordinate is past the view. |
| 52 | // Negative values mean the pointer is at or before the top of the view, and |
| 53 | // positive values mean that the pointer is at or after the bottom of the view. Note |
| 54 | // that top/bottom threshold is added here so that the view still scrolls when the |
| 55 | // pointer are in these buffer pixels. |
| 56 | int pixelsPastView = 0; |
| 57 | |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 58 | final int topBottomThreshold = (int) (mHost.getViewHeight() |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 59 | * TOP_BOTTOM_THRESHOLD_RATIO); |
| 60 | |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 61 | if (mHost.getCurrentPosition().y <= topBottomThreshold) { |
| 62 | pixelsPastView = mHost.getCurrentPosition().y - topBottomThreshold; |
| 63 | } else if (mHost.getCurrentPosition().y >= mHost.getViewHeight() |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 64 | - topBottomThreshold) { |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 65 | pixelsPastView = mHost.getCurrentPosition().y - mHost.getViewHeight() |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 66 | + topBottomThreshold; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 67 | } |
| 68 | |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 69 | if (!mHost.isActive() || pixelsPastView == 0) { |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 70 | // If the operation that started the scrolling is no longer inactive, or if it is active |
| 71 | // but not at the edge of the view, no scrolling is necessary. |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 72 | return; |
| 73 | } |
| 74 | |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 75 | if (pixelsPastView > topBottomThreshold) { |
| 76 | pixelsPastView = topBottomThreshold; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 77 | } |
| 78 | |
| 79 | // Compute the number of pixels to scroll, and scroll that many pixels. |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 80 | final int numPixels = computeScrollDistance(pixelsPastView); |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 81 | mCallbacks.scrollBy(numPixels); |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 82 | |
| 83 | // Remove callback to this, and then properly run at next frame again |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 84 | mCallbacks.removeCallback(this); |
| 85 | mCallbacks.runAtNextFrame(this); |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | /** |
| 89 | * Computes the number of pixels to scroll based on how far the pointer is past the end |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 90 | * of the region. Roughly based on ItemTouchHelper's algorithm for computing the number of |
| 91 | * pixels to scroll when an item is dragged to the end of a view. |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 92 | * @return |
| 93 | */ |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 94 | public int computeScrollDistance(int pixelsPastView) { |
Steve McKay | 5a62037 | 2017-09-11 12:18:56 -0700 | [diff] [blame] | 95 | final int topBottomThreshold = |
| 96 | (int) (mHost.getViewHeight() * TOP_BOTTOM_THRESHOLD_RATIO); |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 97 | |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 98 | final int direction = (int) Math.signum(pixelsPastView); |
| 99 | final int absPastView = Math.abs(pixelsPastView); |
| 100 | |
| 101 | // Calculate the ratio of how far out of the view the pointer currently resides to |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 102 | // the top/bottom scrolling hotspot of the view. |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 103 | final float outOfBoundsRatio = Math.min( |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 104 | 1.0f, (float) absPastView / topBottomThreshold); |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 105 | // Interpolate this ratio and use it to compute the maximum scroll that should be |
| 106 | // possible for this step. |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 107 | final int cappedScrollStep = |
| 108 | (int) (direction * MAX_SCROLL_STEP * smoothOutOfBoundsRatio(outOfBoundsRatio)); |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 109 | |
| 110 | // If the final number of pixels to scroll ends up being 0, the view should still |
| 111 | // scroll at least one pixel. |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 112 | return cappedScrollStep != 0 ? cappedScrollStep : direction; |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Interpolates the given out of bounds ratio on a curve which starts at (0,0) and ends |
| 117 | * at (1,1) and quickly approaches 1 near the start of that interval. This ensures that |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 118 | * drags that are at the edge or barely past the edge of the threshold does little to no |
| 119 | * scrolling, while drags that are near the edge of the view does a lot of |
| 120 | * scrolling. The equation y=x^10 is used, but this could also be tweaked if |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 121 | * needed. |
| 122 | * @param ratio A ratio which is in the range [0, 1]. |
| 123 | * @return A "smoothed" value, also in the range [0, 1]. |
| 124 | */ |
| 125 | private float smoothOutOfBoundsRatio(float ratio) { |
Ben Lin | 3dbd3b1 | 2016-09-27 14:03:04 -0700 | [diff] [blame] | 126 | return (float) Math.pow(ratio, 10); |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 127 | } |
| 128 | |
| 129 | /** |
| 130 | * Used by {@link run} to properly calculate the proper amount of pixels to scroll given time |
| 131 | * passed since scroll started, and to properly scroll / proper listener clean up if necessary. |
| 132 | */ |
Steve McKay | 5a62037 | 2017-09-11 12:18:56 -0700 | [diff] [blame] | 133 | public static abstract class ScrollHost { |
| 134 | public abstract Point getCurrentPosition(); |
| 135 | public abstract int getViewHeight(); |
| 136 | public abstract boolean isActive(); |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 137 | } |
| 138 | |
| 139 | /** |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 140 | * Callback used by scroller to perform UI tasks, such as scrolling and rerunning at next UI |
| 141 | * cycle. |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 142 | */ |
Steve McKay | 5a62037 | 2017-09-11 12:18:56 -0700 | [diff] [blame] | 143 | public static abstract class ScrollerCallbacks { |
| 144 | public void scrollBy(int dy) {} |
| 145 | public void runAtNextFrame(Runnable r) {} |
| 146 | public void removeCallback(Runnable r) {} |
Ben Lin | c5e3e8e | 2016-07-13 18:16:36 -0700 | [diff] [blame] | 147 | } |
Steve McKay | 1239452 | 2017-08-24 14:14:10 -0700 | [diff] [blame] | 148 | } |