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.ValueAnimator; 23 import android.content.Context; 24 import android.widget.OverScroller; 25 import com.android.systemui.recents.RecentsConfiguration; 26 import com.android.systemui.recents.misc.Utilities; 27 28 /* The scrolling logic for a TaskStackView */ 29 public class TaskStackViewScroller { 30 public interface TaskStackViewScrollerCallbacks { onScrollChanged(float p)31 public void onScrollChanged(float p); 32 } 33 34 RecentsConfiguration mConfig; 35 TaskStackViewLayoutAlgorithm mLayoutAlgorithm; 36 TaskStackViewScrollerCallbacks mCb; 37 38 float mStackScrollP; 39 40 OverScroller mScroller; 41 ObjectAnimator mScrollAnimator; 42 float mFinalAnimatedScroll; 43 TaskStackViewScroller(Context context, RecentsConfiguration config, TaskStackViewLayoutAlgorithm layoutAlgorithm)44 public TaskStackViewScroller(Context context, RecentsConfiguration config, TaskStackViewLayoutAlgorithm layoutAlgorithm) { 45 mConfig = config; 46 mScroller = new OverScroller(context); 47 mLayoutAlgorithm = layoutAlgorithm; 48 setStackScroll(getStackScroll()); 49 } 50 51 /** Resets the task scroller. */ reset()52 void reset() { 53 mStackScrollP = 0f; 54 } 55 56 /** Sets the callbacks */ setCallbacks(TaskStackViewScrollerCallbacks cb)57 void setCallbacks(TaskStackViewScrollerCallbacks cb) { 58 mCb = cb; 59 } 60 61 /** Gets the current stack scroll */ getStackScroll()62 public float getStackScroll() { 63 return mStackScrollP; 64 } 65 66 /** Sets the current stack scroll */ setStackScroll(float s)67 public void setStackScroll(float s) { 68 mStackScrollP = s; 69 if (mCb != null) { 70 mCb.onScrollChanged(mStackScrollP); 71 } 72 } 73 74 /** Sets the current stack scroll without calling the callback. */ setStackScrollRaw(float s)75 void setStackScrollRaw(float s) { 76 mStackScrollP = s; 77 } 78 79 /** 80 * Sets the current stack scroll to the initial state when you first enter recents. 81 * @return whether the stack progress changed. 82 */ setStackScrollToInitialState()83 public boolean setStackScrollToInitialState() { 84 float prevStackScrollP = mStackScrollP; 85 setStackScroll(getBoundedStackScroll(mLayoutAlgorithm.mInitialScrollP)); 86 return Float.compare(prevStackScrollP, mStackScrollP) != 0; 87 } 88 89 /** Bounds the current scroll if necessary */ boundScroll()90 public boolean boundScroll() { 91 float curScroll = getStackScroll(); 92 float newScroll = getBoundedStackScroll(curScroll); 93 if (Float.compare(newScroll, curScroll) != 0) { 94 setStackScroll(newScroll); 95 return true; 96 } 97 return false; 98 } 99 /** Bounds the current scroll if necessary, but does not synchronize the stack view with the model. */ boundScrollRaw()100 public boolean boundScrollRaw() { 101 float curScroll = getStackScroll(); 102 float newScroll = getBoundedStackScroll(curScroll); 103 if (Float.compare(newScroll, curScroll) != 0) { 104 setStackScrollRaw(newScroll); 105 return true; 106 } 107 return false; 108 } 109 110 /** Returns the bounded stack scroll */ getBoundedStackScroll(float scroll)111 float getBoundedStackScroll(float scroll) { 112 return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll)); 113 } 114 115 /** Returns the amount that the aboslute value of how much the scroll is out of bounds. */ getScrollAmountOutOfBounds(float scroll)116 float getScrollAmountOutOfBounds(float scroll) { 117 if (scroll < mLayoutAlgorithm.mMinScrollP) { 118 return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP); 119 } else if (scroll > mLayoutAlgorithm.mMaxScrollP) { 120 return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP); 121 } 122 return 0f; 123 } 124 125 /** Returns whether the specified scroll is out of bounds */ isScrollOutOfBounds()126 boolean isScrollOutOfBounds() { 127 return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0; 128 } 129 130 /** Animates the stack scroll into bounds */ animateBoundScroll()131 ObjectAnimator animateBoundScroll() { 132 float curScroll = getStackScroll(); 133 float newScroll = getBoundedStackScroll(curScroll); 134 if (Float.compare(newScroll, curScroll) != 0) { 135 // Start a new scroll animation 136 animateScroll(curScroll, newScroll, null); 137 } 138 return mScrollAnimator; 139 } 140 141 /** Animates the stack scroll */ animateScroll(float curScroll, float newScroll, final Runnable postRunnable)142 void animateScroll(float curScroll, float newScroll, final Runnable postRunnable) { 143 // Finish any current scrolling animations 144 if (mScrollAnimator != null && mScrollAnimator.isRunning()) { 145 setStackScroll(mFinalAnimatedScroll); 146 mScroller.startScroll(0, progressToScrollRange(mFinalAnimatedScroll), 0, 0, 0); 147 } 148 stopScroller(); 149 stopBoundScrollAnimation(); 150 151 mFinalAnimatedScroll = newScroll; 152 mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll); 153 mScrollAnimator.setDuration(mConfig.taskStackScrollDuration); 154 mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator); 155 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 156 @Override 157 public void onAnimationUpdate(ValueAnimator animation) { 158 setStackScroll((Float) animation.getAnimatedValue()); 159 } 160 }); 161 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 162 @Override 163 public void onAnimationEnd(Animator animation) { 164 if (postRunnable != null) { 165 postRunnable.run(); 166 } 167 mScrollAnimator.removeAllListeners(); 168 } 169 }); 170 mScrollAnimator.start(); 171 } 172 173 /** Aborts any current stack scrolls */ stopBoundScrollAnimation()174 void stopBoundScrollAnimation() { 175 Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator); 176 } 177 178 /**** OverScroller ****/ 179 progressToScrollRange(float p)180 int progressToScrollRange(float p) { 181 return (int) (p * mLayoutAlgorithm.mStackVisibleRect.height()); 182 } 183 scrollRangeToProgress(int s)184 float scrollRangeToProgress(int s) { 185 return (float) s / mLayoutAlgorithm.mStackVisibleRect.height(); 186 } 187 188 /** Called from the view draw, computes the next scroll. */ computeScroll()189 boolean computeScroll() { 190 if (mScroller.computeScrollOffset()) { 191 float scroll = scrollRangeToProgress(mScroller.getCurrY()); 192 setStackScrollRaw(scroll); 193 if (mCb != null) { 194 mCb.onScrollChanged(scroll); 195 } 196 return true; 197 } 198 return false; 199 } 200 201 /** Returns whether the overscroller is scrolling. */ isScrolling()202 boolean isScrolling() { 203 return !mScroller.isFinished(); 204 } 205 206 /** Stops the scroller and any current fling. */ stopScroller()207 void stopScroller() { 208 if (!mScroller.isFinished()) { 209 mScroller.abortAnimation(); 210 } 211 } 212 }