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 { onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation)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 TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb, TaskStackLayoutAlgorithm layoutAlgorithm)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. */ reset()88 void reset() { 89 mStackScrollP = 0f; 90 mLastDeltaP = 0f; 91 } 92 resetDeltaScroll()93 void resetDeltaScroll() { 94 mLastDeltaP = 0f; 95 } 96 97 /** Gets the current stack scroll */ getStackScroll()98 public float getStackScroll() { 99 return mStackScrollP; 100 } 101 102 /** 103 * Sets the current stack scroll immediately. 104 */ setStackScroll(float s)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 */ setDeltaStackScroll(float downP, float deltaP)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 */ setStackScroll(float newScroll, AnimationProps animation)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 */ setStackScrollToInitialState()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 */ fling(float downScrollP, int downY, int y, int velY, int minY, int maxY, int overscroll)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 */ boundScroll()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 */ getBoundedStackScroll(float 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. */ getScrollAmountOutOfBounds(float scroll)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 */ isScrollOutOfBounds()185 boolean isScrollOutOfBounds() { 186 return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0; 187 } 188 189 /** Animates the stack scroll into bounds */ animateBoundScroll()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 */ animateScroll(float newScroll, final Runnable postRunnable)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 */ animateScroll(float newScroll, int duration, final Runnable postRunnable)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 */ stopBoundScrollAnimation()241 void stopBoundScrollAnimation() { 242 Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator); 243 } 244 245 /**** OverScroller ****/ 246 247 /** Called from the view draw, computes the next scroll. */ computeScroll()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. */ isScrolling()261 boolean isScrolling() { 262 return !mScroller.isFinished(); 263 } 264 265 /** Stops the scroller and any current fling. */ stopScroller()266 void stopScroller() { 267 if (!mScroller.isFinished()) { 268 mScroller.abortAnimation(); 269 } 270 } 271 dump(String prefix, PrintWriter writer)272 public void dump(String prefix, PrintWriter writer) { 273 writer.print(prefix); writer.print(TAG); 274 writer.print(" stackScroll:"); writer.print(mStackScrollP); 275 writer.println(); 276 } 277 }