blob: f7b062e186192ab8efdbd72358c387f2ab966f32 [file] [log] [blame]
Ben Linc5e3e8e2016-07-13 18:16:36 -07001/*
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 Hsu0c375982018-06-21 22:06:43 +080017package com.android.documentsui;
Ben Linc5e3e8e2016-07-13 18:16:36 -070018
19import android.graphics.Point;
Ben Linc5e3e8e2016-07-13 18:16:36 -070020
21/**
22 * Provides auto-scrolling upon request when user's interaction with the application
Riddle Hsu0c375982018-06-21 22:06:43 +080023 * 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 Linc5e3e8e2016-07-13 18:16:36 -070026 */
27public final class ViewAutoScroller implements Runnable {
Steve McKay12394522017-08-24 14:14:10 -070028
Ben Lin3dbd3b12016-09-27 14:03:04 -070029 // 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 Linc5e3e8e2016-07-13 18:16:36 -070032
Steve McKay12394522017-08-24 14:14:10 -070033 private ScrollHost mHost;
Steve McKay5a620372017-09-11 12:18:56 -070034 private ScrollerCallbacks mCallbacks;
Ben Linc5e3e8e2016-07-13 18:16:36 -070035
Steve McKay5a620372017-09-11 12:18:56 -070036 public ViewAutoScroller(ScrollHost scrollHost, ScrollerCallbacks callbacks) {
Steve McKay12394522017-08-24 14:14:10 -070037 assert scrollHost != null;
38 assert callbacks != null;
39
40 mHost = scrollHost;
41 mCallbacks = callbacks;
Ben Linc5e3e8e2016-07-13 18:16:36 -070042 }
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 McKay12394522017-08-24 14:14:10 -070058 final int topBottomThreshold = (int) (mHost.getViewHeight()
Ben Lin3dbd3b12016-09-27 14:03:04 -070059 * TOP_BOTTOM_THRESHOLD_RATIO);
60
Steve McKay12394522017-08-24 14:14:10 -070061 if (mHost.getCurrentPosition().y <= topBottomThreshold) {
62 pixelsPastView = mHost.getCurrentPosition().y - topBottomThreshold;
63 } else if (mHost.getCurrentPosition().y >= mHost.getViewHeight()
Ben Lin3dbd3b12016-09-27 14:03:04 -070064 - topBottomThreshold) {
Steve McKay12394522017-08-24 14:14:10 -070065 pixelsPastView = mHost.getCurrentPosition().y - mHost.getViewHeight()
Ben Lin3dbd3b12016-09-27 14:03:04 -070066 + topBottomThreshold;
Ben Linc5e3e8e2016-07-13 18:16:36 -070067 }
68
Steve McKay12394522017-08-24 14:14:10 -070069 if (!mHost.isActive() || pixelsPastView == 0) {
Ben Linc5e3e8e2016-07-13 18:16:36 -070070 // 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 Linc5e3e8e2016-07-13 18:16:36 -070072 return;
73 }
74
Ben Lin3dbd3b12016-09-27 14:03:04 -070075 if (pixelsPastView > topBottomThreshold) {
76 pixelsPastView = topBottomThreshold;
Ben Linc5e3e8e2016-07-13 18:16:36 -070077 }
78
79 // Compute the number of pixels to scroll, and scroll that many pixels.
Ben Lin3dbd3b12016-09-27 14:03:04 -070080 final int numPixels = computeScrollDistance(pixelsPastView);
Steve McKay12394522017-08-24 14:14:10 -070081 mCallbacks.scrollBy(numPixels);
Ben Linc5e3e8e2016-07-13 18:16:36 -070082
83 // Remove callback to this, and then properly run at next frame again
Steve McKay12394522017-08-24 14:14:10 -070084 mCallbacks.removeCallback(this);
85 mCallbacks.runAtNextFrame(this);
Ben Linc5e3e8e2016-07-13 18:16:36 -070086 }
87
88 /**
89 * Computes the number of pixels to scroll based on how far the pointer is past the end
Ben Lin3dbd3b12016-09-27 14:03:04 -070090 * 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 Linc5e3e8e2016-07-13 18:16:36 -070092 * @return
93 */
Ben Lin3dbd3b12016-09-27 14:03:04 -070094 public int computeScrollDistance(int pixelsPastView) {
Steve McKay5a620372017-09-11 12:18:56 -070095 final int topBottomThreshold =
96 (int) (mHost.getViewHeight() * TOP_BOTTOM_THRESHOLD_RATIO);
Ben Lin3dbd3b12016-09-27 14:03:04 -070097
Ben Linc5e3e8e2016-07-13 18:16:36 -070098 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 Lin3dbd3b12016-09-27 14:03:04 -0700102 // the top/bottom scrolling hotspot of the view.
Ben Linc5e3e8e2016-07-13 18:16:36 -0700103 final float outOfBoundsRatio = Math.min(
Ben Lin3dbd3b12016-09-27 14:03:04 -0700104 1.0f, (float) absPastView / topBottomThreshold);
Ben Linc5e3e8e2016-07-13 18:16:36 -0700105 // Interpolate this ratio and use it to compute the maximum scroll that should be
106 // possible for this step.
Ben Lin3dbd3b12016-09-27 14:03:04 -0700107 final int cappedScrollStep =
108 (int) (direction * MAX_SCROLL_STEP * smoothOutOfBoundsRatio(outOfBoundsRatio));
Ben Linc5e3e8e2016-07-13 18:16:36 -0700109
110 // If the final number of pixels to scroll ends up being 0, the view should still
111 // scroll at least one pixel.
Ben Lin3dbd3b12016-09-27 14:03:04 -0700112 return cappedScrollStep != 0 ? cappedScrollStep : direction;
Ben Linc5e3e8e2016-07-13 18:16:36 -0700113 }
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 Lin3dbd3b12016-09-27 14:03:04 -0700118 * 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 Linc5e3e8e2016-07-13 18:16:36 -0700121 * 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 Lin3dbd3b12016-09-27 14:03:04 -0700126 return (float) Math.pow(ratio, 10);
Ben Linc5e3e8e2016-07-13 18:16:36 -0700127 }
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 McKay5a620372017-09-11 12:18:56 -0700133 public static abstract class ScrollHost {
134 public abstract Point getCurrentPosition();
135 public abstract int getViewHeight();
136 public abstract boolean isActive();
Ben Linc5e3e8e2016-07-13 18:16:36 -0700137 }
138
139 /**
Steve McKay12394522017-08-24 14:14:10 -0700140 * Callback used by scroller to perform UI tasks, such as scrolling and rerunning at next UI
141 * cycle.
Ben Linc5e3e8e2016-07-13 18:16:36 -0700142 */
Steve McKay5a620372017-09-11 12:18:56 -0700143 public static abstract class ScrollerCallbacks {
144 public void scrollBy(int dy) {}
145 public void runAtNextFrame(Runnable r) {}
146 public void removeCallback(Runnable r) {}
Ben Linc5e3e8e2016-07-13 18:16:36 -0700147 }
Steve McKay12394522017-08-24 14:14:10 -0700148}