TaskStackViewScroller.java revision c041d883c3591670441d4c2da8fd1108dfd28acd
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
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.widget.OverScroller;
25import com.android.systemui.recents.RecentsConfiguration;
26import com.android.systemui.recents.misc.Utilities;
27
28/* The scrolling logic for a TaskStackView */
29public class TaskStackViewScroller {
30    public interface TaskStackViewScrollerCallbacks {
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
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. */
52    void reset() {
53        mStackScrollP = 0f;
54    }
55
56    /** Sets the callbacks */
57    void setCallbacks(TaskStackViewScrollerCallbacks cb) {
58        mCb = cb;
59    }
60
61    /** Gets the current stack scroll */
62    public float getStackScroll() {
63        return mStackScrollP;
64    }
65
66    /** Sets the current stack scroll */
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. */
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     */
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 */
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. */
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 */
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. */
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 */
126    boolean isScrollOutOfBounds() {
127        return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0;
128    }
129
130    /** Animates the stack scroll into bounds */
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 */
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 */
174    void stopBoundScrollAnimation() {
175        Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator);
176    }
177
178    /**** OverScroller ****/
179
180    int progressToScrollRange(float p) {
181        return (int) (p * mLayoutAlgorithm.mStackVisibleRect.height());
182    }
183
184    float scrollRangeToProgress(int s) {
185        return (float) s / mLayoutAlgorithm.mStackVisibleRect.height();
186    }
187
188    /** Called from the view draw, computes the next scroll. */
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. */
202    boolean isScrolling() {
203        return !mScroller.isFinished();
204    }
205
206    /** Stops the scroller and any current fling. */
207    void stopScroller() {
208        if (!mScroller.isFinished()) {
209            mScroller.abortAnimation();
210        }
211    }
212}