Home | History | Annotate | Download | only in views
      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 {
     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 }