1 /* 2 * Copyright (C) 2014 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 17 package com.android.systemui.recents.views; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.TimeInterpolator; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.util.FloatProperty; 26 import android.util.Log; 27 import android.util.Property; 28 import android.view.ViewConfiguration; 29 import android.view.ViewDebug; 30 import android.widget.OverScroller; 31 32 import com.android.systemui.Interpolators; 33 import com.android.systemui.R; 34 import com.android.systemui.recents.LegacyRecentsImpl; 35 import com.android.systemui.recents.utilities.AnimationProps; 36 import com.android.systemui.recents.utilities.Utilities; 37 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm; 38 import com.android.systemui.statusbar.FlingAnimationUtils; 39 40 import java.io.PrintWriter; 41 42 /* The scrolling logic for a TaskStackView */ 43 public class TaskStackViewScroller { 44 45 private static final String TAG = "TaskStackViewScroller"; 46 private static final boolean DEBUG = false; 47 48 public interface TaskStackViewScrollerCallbacks { onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation)49 void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation); 50 } 51 52 /** 53 * A Property wrapper around the <code>stackScroll</code> functionality handled by the 54 * {@link #setStackScroll(float)} and 55 * {@link #getStackScroll()} methods. 56 */ 57 private static final Property<TaskStackViewScroller, Float> STACK_SCROLL = 58 new FloatProperty<TaskStackViewScroller>("stackScroll") { 59 @Override 60 public void setValue(TaskStackViewScroller object, float value) { 61 object.setStackScroll(value); 62 } 63 64 @Override 65 public Float get(TaskStackViewScroller object) { 66 return object.getStackScroll(); 67 } 68 }; 69 70 Context mContext; 71 TaskStackLayoutAlgorithm mLayoutAlgorithm; 72 TaskStackViewScrollerCallbacks mCb; 73 74 @ViewDebug.ExportedProperty(category="recents") 75 float mStackScrollP; 76 @ViewDebug.ExportedProperty(category="recents") 77 float mLastDeltaP = 0f; 78 float mFlingDownScrollP; 79 int mFlingDownY; 80 81 OverScroller mScroller; 82 ObjectAnimator mScrollAnimator; 83 float mFinalAnimatedScroll; 84 85 final FlingAnimationUtils mFlingAnimationUtils; 86 TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb, TaskStackLayoutAlgorithm layoutAlgorithm)87 public TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb, 88 TaskStackLayoutAlgorithm layoutAlgorithm) { 89 mContext = context; 90 mCb = cb; 91 mScroller = new OverScroller(context); 92 if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) { 93 mScroller.setFriction(0.06f); 94 } 95 mLayoutAlgorithm = layoutAlgorithm; 96 mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f); 97 } 98 99 /** Resets the task scroller. */ reset()100 void reset() { 101 mStackScrollP = 0f; 102 mLastDeltaP = 0f; 103 } 104 resetDeltaScroll()105 void resetDeltaScroll() { 106 mLastDeltaP = 0f; 107 } 108 109 /** Gets the current stack scroll */ getStackScroll()110 public float getStackScroll() { 111 return mStackScrollP; 112 } 113 114 /** 115 * Sets the current stack scroll immediately. 116 */ setStackScroll(float s)117 public void setStackScroll(float s) { 118 setStackScroll(s, AnimationProps.IMMEDIATE); 119 } 120 121 /** 122 * Sets the current stack scroll immediately, and returns the difference between the target 123 * scroll and the actual scroll after accounting for the effect on the focus state. 124 */ setDeltaStackScroll(float downP, float deltaP)125 public float setDeltaStackScroll(float downP, float deltaP) { 126 float targetScroll = downP + deltaP; 127 float newScroll = mLayoutAlgorithm.updateFocusStateOnScroll(downP + mLastDeltaP, targetScroll, 128 mStackScrollP); 129 setStackScroll(newScroll, AnimationProps.IMMEDIATE); 130 mLastDeltaP = deltaP; 131 return newScroll - targetScroll; 132 } 133 134 /** 135 * Sets the current stack scroll, but indicates to the callback the preferred animation to 136 * update to this new scroll. 137 */ setStackScroll(float newScroll, AnimationProps animation)138 public void setStackScroll(float newScroll, AnimationProps animation) { 139 float prevScroll = mStackScrollP; 140 mStackScrollP = newScroll; 141 if (mCb != null) { 142 mCb.onStackScrollChanged(prevScroll, mStackScrollP, animation); 143 } 144 } 145 146 /** 147 * Sets the current stack scroll to the initial state when you first enter recents. 148 * @return whether the stack progress changed. 149 */ setStackScrollToInitialState()150 public boolean setStackScrollToInitialState() { 151 float prevScroll = mStackScrollP; 152 setStackScroll(mLayoutAlgorithm.mInitialScrollP); 153 return Float.compare(prevScroll, mStackScrollP) != 0; 154 } 155 156 /** 157 * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}. 158 */ fling(float downScrollP, int downY, int y, int velY, int minY, int maxY, int overscroll)159 public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY, 160 int overscroll) { 161 if (DEBUG) { 162 Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y + 163 ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY); 164 } 165 mFlingDownScrollP = downScrollP; 166 mFlingDownY = downY; 167 mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll); 168 } 169 170 /** Bounds the current scroll if necessary */ boundScroll()171 public boolean boundScroll() { 172 float curScroll = getStackScroll(); 173 float newScroll = getBoundedStackScroll(curScroll); 174 if (Float.compare(newScroll, curScroll) != 0) { 175 setStackScroll(newScroll); 176 return true; 177 } 178 return false; 179 } 180 181 /** Returns the bounded stack scroll */ getBoundedStackScroll(float scroll)182 float getBoundedStackScroll(float scroll) { 183 return Utilities.clamp(scroll, mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP); 184 } 185 186 /** Returns the amount that the absolute value of how much the scroll is out of bounds. */ getScrollAmountOutOfBounds(float scroll)187 float getScrollAmountOutOfBounds(float scroll) { 188 if (scroll < mLayoutAlgorithm.mMinScrollP) { 189 return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP); 190 } else if (scroll > mLayoutAlgorithm.mMaxScrollP) { 191 return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP); 192 } 193 return 0f; 194 } 195 196 /** Returns whether the specified scroll is out of bounds */ isScrollOutOfBounds()197 boolean isScrollOutOfBounds() { 198 return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0; 199 } 200 201 /** 202 * Scrolls the closest task and snaps into place. Only used in recents for low ram devices. 203 * @param velocity of scroll 204 */ scrollToClosestTask(int velocity)205 void scrollToClosestTask(int velocity) { 206 float stackScroll = getStackScroll(); 207 208 // Skip if not in low ram layout and if the scroll is out of min and max bounds 209 if (!LegacyRecentsImpl.getConfiguration().isLowRamDevice || stackScroll < mLayoutAlgorithm.mMinScrollP 210 || stackScroll > mLayoutAlgorithm.mMaxScrollP) { 211 return; 212 } 213 TaskStackLowRamLayoutAlgorithm algorithm = mLayoutAlgorithm.mTaskStackLowRamLayoutAlgorithm; 214 215 float flingThreshold = ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity(); 216 if (Math.abs(velocity) > flingThreshold) { 217 int minY = algorithm.percentageToScroll(mLayoutAlgorithm.mMinScrollP); 218 int maxY = algorithm.percentageToScroll(mLayoutAlgorithm.mMaxScrollP); 219 220 // Calculate the fling and snap to closest task from final y position, computeScroll() 221 // never runs when cancelled with animateScroll() and the overscroll is not calculated 222 // here 223 fling(0 /* downScrollP */, 0 /* downY */, algorithm.percentageToScroll(stackScroll), 224 -velocity, minY, maxY, 0 /* overscroll */); 225 float pos = algorithm.scrollToPercentage(mScroller.getFinalY()); 226 227 float newScrollP = algorithm.getClosestTaskP(pos, mLayoutAlgorithm.mNumStackTasks, 228 velocity); 229 ValueAnimator animator = ObjectAnimator.ofFloat(stackScroll, newScrollP); 230 mFlingAnimationUtils.apply(animator, algorithm.percentageToScroll(stackScroll), 231 algorithm.percentageToScroll(newScrollP), velocity); 232 animateScroll(newScrollP, (int) animator.getDuration(), animator.getInterpolator(), 233 null /* postRunnable */); 234 } else { 235 float newScrollP = algorithm.getClosestTaskP(stackScroll, 236 mLayoutAlgorithm.mNumStackTasks, velocity); 237 animateScroll(newScrollP, 300, Interpolators.ACCELERATE_DECELERATE, 238 null /* postRunnable */); 239 } 240 } 241 242 /** Animates the stack scroll into bounds */ animateBoundScroll()243 ObjectAnimator animateBoundScroll() { 244 // TODO: Take duration for snap back 245 float curScroll = getStackScroll(); 246 float newScroll = getBoundedStackScroll(curScroll); 247 if (Float.compare(newScroll, curScroll) != 0) { 248 // Start a new scroll animation 249 animateScroll(newScroll, null /* postScrollRunnable */); 250 } 251 return mScrollAnimator; 252 } 253 254 /** Animates the stack scroll */ animateScroll(float newScroll, final Runnable postRunnable)255 void animateScroll(float newScroll, final Runnable postRunnable) { 256 int duration = mContext.getResources().getInteger( 257 R.integer.recents_animate_task_stack_scroll_duration); 258 animateScroll(newScroll, duration, postRunnable); 259 } 260 261 /** Animates the stack scroll */ animateScroll(float newScroll, int duration, final Runnable postRunnable)262 void animateScroll(float newScroll, int duration, final Runnable postRunnable) { 263 animateScroll(newScroll, duration, Interpolators.LINEAR_OUT_SLOW_IN, postRunnable); 264 } 265 266 /** Animates the stack scroll with time interpolator */ animateScroll(float newScroll, int duration, TimeInterpolator interpolator, final Runnable postRunnable)267 void animateScroll(float newScroll, int duration, TimeInterpolator interpolator, 268 final Runnable postRunnable) { 269 ObjectAnimator an = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll); 270 an.setDuration(duration); 271 an.setInterpolator(interpolator); 272 animateScroll(newScroll, an, postRunnable); 273 } 274 275 /** Animates the stack scroll with animator */ animateScroll(float newScroll, ObjectAnimator animator, final Runnable postRunnable)276 private void animateScroll(float newScroll, ObjectAnimator animator, 277 final Runnable postRunnable) { 278 // Finish any current scrolling animations 279 if (mScrollAnimator != null && mScrollAnimator.isRunning()) { 280 setStackScroll(mFinalAnimatedScroll); 281 mScroller.forceFinished(true); 282 } 283 stopScroller(); 284 stopBoundScrollAnimation(); 285 286 if (Float.compare(mStackScrollP, newScroll) != 0) { 287 mFinalAnimatedScroll = newScroll; 288 mScrollAnimator = animator; 289 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 290 @Override 291 public void onAnimationEnd(Animator animation) { 292 if (postRunnable != null) { 293 postRunnable.run(); 294 } 295 mScrollAnimator.removeAllListeners(); 296 } 297 }); 298 mScrollAnimator.start(); 299 } else { 300 if (postRunnable != null) { 301 postRunnable.run(); 302 } 303 } 304 } 305 306 /** Aborts any current stack scrolls */ stopBoundScrollAnimation()307 void stopBoundScrollAnimation() { 308 Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator); 309 } 310 311 /**** OverScroller ****/ 312 313 /** Called from the view draw, computes the next scroll. */ computeScroll()314 boolean computeScroll() { 315 if (mScroller.computeScrollOffset()) { 316 float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY()); 317 mFlingDownScrollP += setDeltaStackScroll(mFlingDownScrollP, deltaP); 318 if (DEBUG) { 319 Log.d(TAG, "computeScroll: " + (mFlingDownScrollP + deltaP)); 320 } 321 return true; 322 } 323 return false; 324 } 325 326 /** Returns whether the overscroller is scrolling. */ isScrolling()327 boolean isScrolling() { 328 return !mScroller.isFinished(); 329 } 330 getScrollVelocity()331 float getScrollVelocity() { 332 return mScroller.getCurrVelocity(); 333 } 334 335 /** Stops the scroller and any current fling. */ stopScroller()336 void stopScroller() { 337 if (!mScroller.isFinished()) { 338 mScroller.abortAnimation(); 339 } 340 } 341 dump(String prefix, PrintWriter writer)342 public void dump(String prefix, PrintWriter writer) { 343 writer.print(prefix); writer.print(TAG); 344 writer.print(" stackScroll:"); writer.print(mStackScrollP); 345 writer.println(); 346 } 347 }