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