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.content.Context; 23 import android.util.FloatProperty; 24 import android.util.Log; 25 import android.util.MutableFloat; 26 import android.util.Property; 27 import android.view.ViewDebug; 28 import android.widget.OverScroller; 29 30 import com.android.systemui.Interpolators; 31 import com.android.systemui.R; 32 import com.android.systemui.recents.misc.Utilities; 33 34 import java.io.PrintWriter; 35 36 /* The scrolling logic for a TaskStackView */ 37 public class TaskStackViewScroller { 38 39 private static final String TAG = "TaskStackViewScroller"; 40 private static final boolean DEBUG = false; 41 42 public interface TaskStackViewScrollerCallbacks { 43 void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation); 44 } 45 46 /** 47 * A Property wrapper around the <code>stackScroll</code> functionality handled by the 48 * {@link #setStackScroll(float)} and 49 * {@link #getStackScroll()} methods. 50 */ 51 private static final Property<TaskStackViewScroller, Float> STACK_SCROLL = 52 new FloatProperty<TaskStackViewScroller>("stackScroll") { 53 @Override 54 public void setValue(TaskStackViewScroller object, float value) { 55 object.setStackScroll(value); 56 } 57 58 @Override 59 public Float get(TaskStackViewScroller object) { 60 return object.getStackScroll(); 61 } 62 }; 63 64 Context mContext; 65 TaskStackLayoutAlgorithm mLayoutAlgorithm; 66 TaskStackViewScrollerCallbacks mCb; 67 68 @ViewDebug.ExportedProperty(category="recents") 69 float mStackScrollP; 70 @ViewDebug.ExportedProperty(category="recents") 71 float mLastDeltaP = 0f; 72 float mFlingDownScrollP; 73 int mFlingDownY; 74 75 OverScroller mScroller; 76 ObjectAnimator mScrollAnimator; 77 float mFinalAnimatedScroll; 78 79 public TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb, 80 TaskStackLayoutAlgorithm layoutAlgorithm) { 81 mContext = context; 82 mCb = cb; 83 mScroller = new OverScroller(context); 84 mLayoutAlgorithm = layoutAlgorithm; 85 } 86 87 /** Resets the task scroller. */ 88 void reset() { 89 mStackScrollP = 0f; 90 mLastDeltaP = 0f; 91 } 92 93 void resetDeltaScroll() { 94 mLastDeltaP = 0f; 95 } 96 97 /** Gets the current stack scroll */ 98 public float getStackScroll() { 99 return mStackScrollP; 100 } 101 102 /** 103 * Sets the current stack scroll immediately. 104 */ 105 public void setStackScroll(float s) { 106 setStackScroll(s, AnimationProps.IMMEDIATE); 107 } 108 109 /** 110 * Sets the current stack scroll immediately, and returns the difference between the target 111 * scroll and the actual scroll after accounting for the effect on the focus state. 112 */ 113 public float setDeltaStackScroll(float downP, float deltaP) { 114 float targetScroll = downP + deltaP; 115 float newScroll = mLayoutAlgorithm.updateFocusStateOnScroll(downP + mLastDeltaP, targetScroll, 116 mStackScrollP); 117 setStackScroll(newScroll, AnimationProps.IMMEDIATE); 118 mLastDeltaP = deltaP; 119 return newScroll - targetScroll; 120 } 121 122 /** 123 * Sets the current stack scroll, but indicates to the callback the preferred animation to 124 * update to this new scroll. 125 */ 126 public void setStackScroll(float newScroll, AnimationProps animation) { 127 float prevScroll = mStackScrollP; 128 mStackScrollP = newScroll; 129 if (mCb != null) { 130 mCb.onStackScrollChanged(prevScroll, mStackScrollP, animation); 131 } 132 } 133 134 /** 135 * Sets the current stack scroll to the initial state when you first enter recents. 136 * @return whether the stack progress changed. 137 */ 138 public boolean setStackScrollToInitialState() { 139 float prevScroll = mStackScrollP; 140 setStackScroll(mLayoutAlgorithm.mInitialScrollP); 141 return Float.compare(prevScroll, mStackScrollP) != 0; 142 } 143 144 /** 145 * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}. 146 */ 147 public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY, 148 int overscroll) { 149 if (DEBUG) { 150 Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y + 151 ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY); 152 } 153 mFlingDownScrollP = downScrollP; 154 mFlingDownY = downY; 155 mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll); 156 } 157 158 /** Bounds the current scroll if necessary */ 159 public boolean boundScroll() { 160 float curScroll = getStackScroll(); 161 float newScroll = getBoundedStackScroll(curScroll); 162 if (Float.compare(newScroll, curScroll) != 0) { 163 setStackScroll(newScroll); 164 return true; 165 } 166 return false; 167 } 168 169 /** Returns the bounded stack scroll */ 170 float getBoundedStackScroll(float scroll) { 171 return Utilities.clamp(scroll, mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP); 172 } 173 174 /** Returns the amount that the absolute value of how much the scroll is out of bounds. */ 175 float getScrollAmountOutOfBounds(float scroll) { 176 if (scroll < mLayoutAlgorithm.mMinScrollP) { 177 return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP); 178 } else if (scroll > mLayoutAlgorithm.mMaxScrollP) { 179 return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP); 180 } 181 return 0f; 182 } 183 184 /** Returns whether the specified scroll is out of bounds */ 185 boolean isScrollOutOfBounds() { 186 return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0; 187 } 188 189 /** Animates the stack scroll into bounds */ 190 ObjectAnimator animateBoundScroll() { 191 // TODO: Take duration for snap back 192 float curScroll = getStackScroll(); 193 float newScroll = getBoundedStackScroll(curScroll); 194 if (Float.compare(newScroll, curScroll) != 0) { 195 // Start a new scroll animation 196 animateScroll(newScroll, null /* postScrollRunnable */); 197 } 198 return mScrollAnimator; 199 } 200 201 /** Animates the stack scroll */ 202 void animateScroll(float newScroll, final Runnable postRunnable) { 203 int duration = mContext.getResources().getInteger( 204 R.integer.recents_animate_task_stack_scroll_duration); 205 animateScroll(newScroll, duration, postRunnable); 206 } 207 208 /** Animates the stack scroll */ 209 void animateScroll(float newScroll, int duration, final Runnable postRunnable) { 210 // Finish any current scrolling animations 211 if (mScrollAnimator != null && mScrollAnimator.isRunning()) { 212 setStackScroll(mFinalAnimatedScroll); 213 mScroller.forceFinished(true); 214 } 215 stopScroller(); 216 stopBoundScrollAnimation(); 217 218 if (Float.compare(mStackScrollP, newScroll) != 0) { 219 mFinalAnimatedScroll = newScroll; 220 mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll); 221 mScrollAnimator.setDuration(duration); 222 mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 223 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 224 @Override 225 public void onAnimationEnd(Animator animation) { 226 if (postRunnable != null) { 227 postRunnable.run(); 228 } 229 mScrollAnimator.removeAllListeners(); 230 } 231 }); 232 mScrollAnimator.start(); 233 } else { 234 if (postRunnable != null) { 235 postRunnable.run(); 236 } 237 } 238 } 239 240 /** Aborts any current stack scrolls */ 241 void stopBoundScrollAnimation() { 242 Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator); 243 } 244 245 /**** OverScroller ****/ 246 247 /** Called from the view draw, computes the next scroll. */ 248 boolean computeScroll() { 249 if (mScroller.computeScrollOffset()) { 250 float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY()); 251 mFlingDownScrollP += setDeltaStackScroll(mFlingDownScrollP, deltaP); 252 if (DEBUG) { 253 Log.d(TAG, "computeScroll: " + (mFlingDownScrollP + deltaP)); 254 } 255 return true; 256 } 257 return false; 258 } 259 260 /** Returns whether the overscroller is scrolling. */ 261 boolean isScrolling() { 262 return !mScroller.isFinished(); 263 } 264 265 float getScrollVelocity() { 266 return mScroller.getCurrVelocity(); 267 } 268 269 /** Stops the scroller and any current fling. */ 270 void stopScroller() { 271 if (!mScroller.isFinished()) { 272 mScroller.abortAnimation(); 273 } 274 } 275 276 public void dump(String prefix, PrintWriter writer) { 277 writer.print(prefix); writer.print(TAG); 278 writer.print(" stackScroll:"); writer.print(mStackScrollP); 279 writer.println(); 280 } 281 }