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 100 /** Returns the bounded stack scroll */ getBoundedStackScroll(float scroll)101 float getBoundedStackScroll(float scroll) { 102 return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll)); 103 } 104 105 /** Returns the amount that the absolute value of how much the scroll is out of bounds. */ getScrollAmountOutOfBounds(float scroll)106 float getScrollAmountOutOfBounds(float scroll) { 107 if (scroll < mLayoutAlgorithm.mMinScrollP) { 108 return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP); 109 } else if (scroll > mLayoutAlgorithm.mMaxScrollP) { 110 return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP); 111 } 112 return 0f; 113 } 114 115 /** Returns whether the specified scroll is out of bounds */ isScrollOutOfBounds()116 boolean isScrollOutOfBounds() { 117 return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0; 118 } 119 120 /** Animates the stack scroll into bounds */ animateBoundScroll()121 ObjectAnimator animateBoundScroll() { 122 float curScroll = getStackScroll(); 123 float newScroll = getBoundedStackScroll(curScroll); 124 if (Float.compare(newScroll, curScroll) != 0) { 125 // Start a new scroll animation 126 animateScroll(curScroll, newScroll, null); 127 } 128 return mScrollAnimator; 129 } 130 131 /** Animates the stack scroll */ animateScroll(float curScroll, float newScroll, final Runnable postRunnable)132 void animateScroll(float curScroll, float newScroll, final Runnable postRunnable) { 133 // Finish any current scrolling animations 134 if (mScrollAnimator != null && mScrollAnimator.isRunning()) { 135 setStackScroll(mFinalAnimatedScroll); 136 mScroller.startScroll(0, progressToScrollRange(mFinalAnimatedScroll), 0, 0, 0); 137 } 138 stopScroller(); 139 stopBoundScrollAnimation(); 140 141 mFinalAnimatedScroll = newScroll; 142 mScrollAnimator = ObjectAnimator.ofFloat(this, "stackScroll", curScroll, newScroll); 143 mScrollAnimator.setDuration(mConfig.taskStackScrollDuration); 144 mScrollAnimator.setInterpolator(mConfig.linearOutSlowInInterpolator); 145 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 146 @Override 147 public void onAnimationUpdate(ValueAnimator animation) { 148 setStackScroll((Float) animation.getAnimatedValue()); 149 } 150 }); 151 mScrollAnimator.addListener(new AnimatorListenerAdapter() { 152 @Override 153 public void onAnimationEnd(Animator animation) { 154 if (postRunnable != null) { 155 postRunnable.run(); 156 } 157 mScrollAnimator.removeAllListeners(); 158 } 159 }); 160 mScrollAnimator.start(); 161 } 162 163 /** Aborts any current stack scrolls */ stopBoundScrollAnimation()164 void stopBoundScrollAnimation() { 165 Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator); 166 } 167 168 /**** OverScroller ****/ 169 progressToScrollRange(float p)170 int progressToScrollRange(float p) { 171 return (int) (p * mLayoutAlgorithm.mStackVisibleRect.height()); 172 } 173 scrollRangeToProgress(int s)174 float scrollRangeToProgress(int s) { 175 return (float) s / mLayoutAlgorithm.mStackVisibleRect.height(); 176 } 177 178 /** Called from the view draw, computes the next scroll. */ computeScroll()179 boolean computeScroll() { 180 if (mScroller.computeScrollOffset()) { 181 float scroll = scrollRangeToProgress(mScroller.getCurrY()); 182 setStackScrollRaw(scroll); 183 if (mCb != null) { 184 mCb.onScrollChanged(scroll); 185 } 186 return true; 187 } 188 return false; 189 } 190 191 /** Returns whether the overscroller is scrolling. */ isScrolling()192 boolean isScrolling() { 193 return !mScroller.isFinished(); 194 } 195 196 /** Stops the scroller and any current fling. */ stopScroller()197 void stopScroller() { 198 if (!mScroller.isFinished()) { 199 mScroller.abortAnimation(); 200 } 201 } 202 }