1c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen/* Copyright (C) 2010 The Android Open Source Project
244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Licensed under the Apache License, Version 2.0 (the "License");
444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * you may not use this file except in compliance with the License.
544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * You may obtain a copy of the License at
644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *      http://www.apache.org/licenses/LICENSE-2.0
844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Unless required by applicable law or agreed to in writing, software
1044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * distributed under the License is distributed on an "AS IS" BASIS,
1144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * See the License for the specific language governing permissions and
1344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * limitations under the License.
1444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
1544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpackage android.widget;
1744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
18c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohenimport java.lang.ref.WeakReference;
19c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen
20a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator;
21e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guyimport android.animation.PropertyValuesHolder;
2244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context;
2326f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohenimport android.content.res.TypedArray;
2432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap;
25839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter;
2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas;
2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix;
2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint;
2932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff;
3032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode;
3144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect;
329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF;
33d51bbb5b56446519db88f49f2682da782b0069acAdam Cohenimport android.graphics.Region;
34839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter;
3548d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganovimport android.os.Bundle;
3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet;
3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log;
38a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohenimport android.view.InputDevice;
3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent;
4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker;
4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View;
4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration;
4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup;
448a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganovimport android.view.accessibility.AccessibilityEvent;
458a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganovimport android.view.accessibility.AccessibilityNodeInfo;
46b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator;
4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView;
4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView
5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/**
5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe
5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children.
5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator {
5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final String TAG = "StackView";
5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
5844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Default animation parameters
5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
60e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int DEFAULT_ANIMATION_DURATION = 400;
61e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int MINIMUM_ANIMATION_DURATION = 50;
62c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen    private static final int STACK_RELAYOUT_DURATION = 100;
6344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
65839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Parameters effecting the perspective visuals
66839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
67026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_Y = 0.1f;
68026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_X = 0.1f;
69026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
7036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mPerspectiveShiftX;
7136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mPerspectiveShiftY;
7236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mNewPerspectiveShiftX;
7336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mNewPerspectiveShiftY;
7436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
75e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    @SuppressWarnings({"FieldCanBeLocal"})
7678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    private static final float PERSPECTIVE_SCALE_FACTOR = 0f;
77839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
78839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
79839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Represent the two possible stack modes, one where items slide up, and the other
80839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * where items slide down. The perspective is also inverted between these two modes.
81839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
82839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_UP = 0;
83839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_DOWN = 1;
84839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
85839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
8644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These specify the different gesture states
8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
885b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_NONE = 0;
895b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_UP = 1;
905b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_DOWN = 2;
9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Specifies how far you need to swipe (up or down) before it
9444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * will be consider a completed gesture when you lift your finger
9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
96a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    private static final float SWIPE_THRESHOLD_RATIO = 0.2f;
97a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen
98a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    /**
99a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * Specifies the total distance, relative to the size of the stack,
100a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * that views will be slid, either up or down
101a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     */
1025b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SLIDE_UP_RATIO = 0.7f;
10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Sentinel value for no current active pointer.
10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Used by {@link #mActivePointerId}.
10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private static final int INVALID_POINTER = -1;
10944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
111839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
112839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
113839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int NUM_ACTIVE_VIEWS = 5;
114839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
115dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private static final int FRAME_PADDING = 4;
116839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
117e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy    private final Rect mTouchRect = new Rect();
118e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
11926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000;
12026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
1214725a90e8ea82799fb88fd27db5bb16969ac5abfRomain Guy    private static final long MIN_TIME_BETWEEN_SCROLLS = 100;
122a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen
123839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These variables are all related to the current state of touch interaction
12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * with the stack
12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialY;
12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialX;
12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mActivePointerId;
13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mYVelocity = 0;
13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeGestureType = GESTURE_NONE;
132dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mSlideAmount;
13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeThreshold;
13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mTouchSlop;
13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mMaximumVelocity;
13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private VelocityTracker mVelocityTracker;
1373352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private boolean mTransitionIsSetup = false;
13826f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen    private int mResOutColor;
13926f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen    private int mClickColor;
14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static HolographicHelper sHolographicHelper;
14232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private ImageView mHighlight;
1438baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private ImageView mClickFeedback;
1448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private boolean mClickFeedbackIsValid = false;
14532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private StackSlider mStackSlider;
14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private boolean mFirstLayoutHappened = false;
14726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private long mLastInteractionTime = 0;
148a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen    private long mLastScrollTime;
149839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private int mStackMode;
150dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mFramePadding;
1510ac116b688380489c3690f6f65b282990c221f17Adam Cohen    private final Rect stackInvalidateRect = new Rect();
15244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
153abc8b50a26282689c66562cf619563744d2855d1Winson Chung    /**
154abc8b50a26282689c66562cf619563744d2855d1Winson Chung     * {@inheritDoc}
155abc8b50a26282689c66562cf619563744d2855d1Winson Chung     */
15644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context) {
15726f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        this(context, null);
15844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
15944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
160abc8b50a26282689c66562cf619563744d2855d1Winson Chung    /**
161abc8b50a26282689c66562cf619563744d2855d1Winson Chung     * {@inheritDoc}
162abc8b50a26282689c66562cf619563744d2855d1Winson Chung     */
16344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context, AttributeSet attrs) {
16426f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        this(context, attrs, com.android.internal.R.attr.stackViewStyle);
16526f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen    }
16626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen
167abc8b50a26282689c66562cf619563744d2855d1Winson Chung    /**
168abc8b50a26282689c66562cf619563744d2855d1Winson Chung     * {@inheritDoc}
169abc8b50a26282689c66562cf619563744d2855d1Winson Chung     */
17026f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen    public StackView(Context context, AttributeSet attrs, int defStyleAttr) {
171617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette        this(context, attrs, defStyleAttr, 0);
172617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette    }
173617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette
174617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette    /**
175617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette     * {@inheritDoc}
176617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette     */
177617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette    public StackView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
178617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette        super(context, attrs, defStyleAttr, defStyleRes);
179617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette        final TypedArray a = context.obtainStyledAttributes(
180617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette                attrs, com.android.internal.R.styleable.StackView, defStyleAttr, defStyleRes);
18126f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen
18226f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        mResOutColor = a.getColor(
18326f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen                com.android.internal.R.styleable.StackView_resOutColor, 0);
18426f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        mClickColor = a.getColor(
18526f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen                com.android.internal.R.styleable.StackView_clickColor, 0);
18626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen
18726f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        a.recycle();
18844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
18944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
19044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
19144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void initStackView() {
19296d8d56302da81b24333b204e6d7f15064538036Adam Cohen        configureViewAnimator(NUM_ACTIVE_VIEWS, 1);
19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        setStaticTransformationsEnabled(true);
19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
195b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        mTouchSlop = configuration.getScaledTouchSlop();
19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
19744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
19832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
19932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight = new ImageView(getContext());
20032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
20132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
2028baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
2038baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback = new ImageView(getContext());
2048baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback));
2058baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback));
2068baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
2078baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
20832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mStackSlider = new StackSlider();
20932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
2109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (sHolographicHelper == null) {
211dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            sHolographicHelper = new HolographicHelper(mContext);
21232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
2139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipChildren(false);
2149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipToPadding(false);
2151480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen
216839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // This sets the form of the StackView, which is currently to have the perspective-shifted
217839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // views above the active view, and have items slide down when sliding out. The opposite is
218839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // available by using ITEMS_SLIDE_UP.
219839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        mStackMode = ITEMS_SLIDE_DOWN;
220839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
2211480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // This is a flag to indicate the the stack is loading for the first time
2221480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        mWhichChild = -1;
223dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
224dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // Adjust the frame padding based on the density, since the highlight changes based
225dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // on the density
226dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        final float density = mContext.getResources().getDisplayMetrics().density;
227dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
22844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
22944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
23044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
23144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
23244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
23378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    void transformViewForTransition(int fromIndex, int toIndex, final View view, boolean animate) {
23478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (!animate) {
23578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelSliderAnimator();
23678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            view.setRotationX(0f);
23778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
23878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setVerticalOffset(0);
23978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setHorizontalOffset(0);
24069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        }
24169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
242502045849f760f018f662d752020aef32d4cecd9Adam Cohen        if (fromIndex == -1 && toIndex == getNumActiveViews() -1) {
243502045849f760f018f662d752020aef32d4cecd9Adam Cohen            transformViewAtIndex(toIndex, view, false);
244b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(VISIBLE);
245c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen            view.setAlpha(1.0f);
24696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 0 && toIndex == 1) {
24744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item in
24878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelSliderAnimator();
24944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setVisibility(VISIBLE);
25032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
251839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
252b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
25369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            animationSlider.setView(view);
25478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
25578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (animate) {
25678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f);
25778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
25878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator slideIn = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
25978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                        slideInX, slideInY);
26078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideIn.setDuration(duration);
26178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideIn.setInterpolator(new LinearInterpolator());
26278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setSliderAnimator(slideIn);
26378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideIn.start();
26478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            } else {
26578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setYProgress(0f);
26678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setXProgress(0f);
26778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
26896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 1 && toIndex == 0) {
26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item out
27078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelSliderAnimator();
271839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
273b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
27469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            animationSlider.setView(view);
27578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (animate) {
27678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f);
27778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
27878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator slideOut = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
27978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                        slideOutX, slideOutY);
28078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideOut.setDuration(duration);
28178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideOut.setInterpolator(new LinearInterpolator());
28278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setSliderAnimator(slideOut);
28378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideOut.start();
28478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            } else {
28578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setYProgress(1.0f);
28678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setXProgress(0f);
28778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
28869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        } else if (toIndex == 0) {
28944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Make sure this view that is "waiting in the wings" is invisible
29044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setAlpha(0.0f);
291b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(INVISIBLE);
29278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        } else if ((fromIndex == 0 || fromIndex == 1) && toIndex > 1) {
29369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setVisibility(VISIBLE);
29469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setAlpha(1.0f);
29578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            view.setRotationX(0f);
29678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
29778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setVerticalOffset(0);
29878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setHorizontalOffset(0);
299c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen        } else if (fromIndex == -1) {
300c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setAlpha(1.0f);
301c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setVisibility(VISIBLE);
30244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (toIndex == -1) {
30378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (animate) {
304c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen                postDelayed(new Runnable() {
305c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen                    public void run() {
306c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen                        view.setAlpha(0);
307c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen                    }
308c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen                }, STACK_RELAYOUT_DURATION);
30978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            } else {
31078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                view.setAlpha(0f);
31178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
31244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
313839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
314839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // Implement the faked perspective
315839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (toIndex != -1) {
31678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            transformViewAtIndex(toIndex, view, animate);
317f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        }
318f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    }
319839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
32036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private void transformViewAtIndex(int index, final View view, boolean animate) {
32136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float maxPerspectiveShiftY = mPerspectiveShiftY;
32236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float maxPerspectiveShiftX = mPerspectiveShiftX;
323026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
324c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        if (mStackMode == ITEMS_SLIDE_DOWN) {
325c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            index = mMaxNumActiveViews - index - 1;
326c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            if (index == mMaxNumActiveViews - 1) index--;
327c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        } else {
328c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            index--;
329c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            if (index < 0) index++;
330c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        }
331026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
332026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float r = (index * 1.0f) / (mMaxNumActiveViews - 2);
333026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
33436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
335026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
336c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        float perspectiveTranslationY = r * maxPerspectiveShiftY;
337c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        float scaleShiftCorrectionY = (scale - 1) *
338026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR_Y) / 2.0f);
33936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float transY = perspectiveTranslationY + scaleShiftCorrectionY;
340026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
341026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float perspectiveTranslationX = (1 - r) * maxPerspectiveShiftX;
342026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float scaleShiftCorrectionX =  (1 - scale) *
343026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredWidth() * (1 - PERSPECTIVE_SHIFT_FACTOR_X) / 2.0f);
34436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float transX = perspectiveTranslationX + scaleShiftCorrectionX;
34536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
346c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        // If this view is currently being animated for a certain position, we need to cancel
34769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        // this animation so as not to interfere with the new transformation.
34878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (view instanceof StackFrame) {
34978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelTransformAnimator();
35069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        }
35169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
35236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (animate) {
35336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", transX);
35436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY);
35536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder scalePropX = PropertyValuesHolder.ofFloat("scaleX", scale);
35636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder scalePropY = PropertyValuesHolder.ofFloat("scaleY", scale);
35736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
35836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(view, scalePropX, scalePropY,
35936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    translationY, translationX);
360c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            oa.setDuration(STACK_RELAYOUT_DURATION);
36178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (view instanceof StackFrame) {
36278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setTransformAnimator(oa);
36378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
36436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            oa.start();
36536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        } else {
36636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setTranslationX(transX);
36736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setTranslationY(transY);
36836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setScaleX(scale);
36936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setScaleY(scale);
37036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
371026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    }
372026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
3733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private void setupStackSlider(View v, int mode) {
3743352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mStackSlider.setMode(mode);
3753352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (v != null) {
37626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen            mHighlight.setImageBitmap(sHolographicHelper.createResOutline(v, mResOutColor));
3773352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setRotation(v.getRotation());
3783352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setTranslationY(v.getTranslationY());
379026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            mHighlight.setTranslationX(v.getTranslationX());
3803352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.bringToFront();
3813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.bringToFront();
3823352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mStackSlider.setView(v);
3833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3843352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.setVisibility(VISIBLE);
3853352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3863352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3873352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3880e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
3890e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
3900e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
3913352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
3923352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
3933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showNext() {
3940ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
3953352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
3963352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(1);
3973352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
3983352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
3993352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(0);
4003352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
4013352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
4023352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
4033352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showNext();
4043352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
4053352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
4060e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
4070e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
4080e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
4093352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
4103352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
4113352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showPrevious() {
4120ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
4133352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
4143352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(0);
4153352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
4163352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
4173352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(1);
4183352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
4193352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
4203352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
4213352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showPrevious();
4223352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
4233352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
42496d8d56302da81b24333b204e6d7f15064538036Adam Cohen    @Override
425ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen    void showOnly(int childIndex, boolean animate) {
426ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen        super.showOnly(childIndex, animate);
42796d8d56302da81b24333b204e6d7f15064538036Adam Cohen
42896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        // Here we need to make sure that the z-order of the children is correct
4293352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
43096d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int index = modulo(i, getWindowSize());
431d38a0cee2074fcf8005fc0d746aad282941a39b8Adam Cohen            ViewAndMetaData vm = mViewsMap.get(index);
432d38a0cee2074fcf8005fc0d746aad282941a39b8Adam Cohen            if (vm != null) {
4330ac116b688380489c3690f6f65b282990c221f17Adam Cohen                View v = mViewsMap.get(index).view;
4340ac116b688380489c3690f6f65b282990c221f17Adam Cohen                if (v != null) v.bringToFront();
4350ac116b688380489c3690f6f65b282990c221f17Adam Cohen            }
43696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        }
43778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (mHighlight != null) {
43878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            mHighlight.bringToFront();
43978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
4403352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mTransitionIsSetup = false;
4418baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedbackIsValid = false;
4428baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4438baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void updateClickFeedback() {
4458baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        if (!mClickFeedbackIsValid) {
4469c295482dd739e80dd49ea0dd3102ad6be6742ddAdam Cohen            View v = getViewAtRelativeIndex(1);
4478baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (v != null) {
44826f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen                mClickFeedback.setImageBitmap(
44926f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen                        sHolographicHelper.createClickOutline(v, mClickColor));
4508baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationX(v.getTranslationX());
4518baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationY(v.getTranslationY());
4528baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
4538baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mClickFeedbackIsValid = true;
4548baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
4558baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4568baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4578baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
4588baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void showTapFeedback(View v) {
4598baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        updateClickFeedback();
4608baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(VISIBLE);
4618baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.bringToFront();
4628baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
4638baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4648baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4658baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
4668baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void hideTapFeedback(View v) {
4678baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
4688baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
46996d8d56302da81b24333b204e6d7f15064538036Adam Cohen    }
47096d8d56302da81b24333b204e6d7f15064538036Adam Cohen
471f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void updateChildTransforms() {
47296d8d56302da81b24333b204e6d7f15064538036Adam Cohen        for (int i = 0; i < getNumActiveViews(); i++) {
473f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            View v = getViewAtRelativeIndex(i);
474f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            if (v != null) {
47536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                transformViewAtIndex(i, v, false);
476f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            }
477839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
47844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
47944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
48078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    private static class StackFrame extends FrameLayout {
48178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        WeakReference<ObjectAnimator> transformAnimator;
48278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        WeakReference<ObjectAnimator> sliderAnimator;
48378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
48478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        public StackFrame(Context context) {
48578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            super(context);
48678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
48778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
48878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        void setTransformAnimator(ObjectAnimator oa) {
48978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            transformAnimator = new WeakReference<ObjectAnimator>(oa);
49078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
49178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
49278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        void setSliderAnimator(ObjectAnimator oa) {
49378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            sliderAnimator = new WeakReference<ObjectAnimator>(oa);
49478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
49578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
49678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        boolean cancelTransformAnimator() {
49778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (transformAnimator != null) {
49878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator oa = transformAnimator.get();
49978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                if (oa != null) {
50078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    oa.cancel();
50178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    return true;
50278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                }
50378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
50478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            return false;
50578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
50678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
50778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        boolean cancelSliderAnimator() {
50878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (sliderAnimator != null) {
50978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator oa = sliderAnimator.get();
51078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                if (oa != null) {
51178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    oa.cancel();
51278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    return true;
51378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                }
51478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
51578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            return false;
51678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
51778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    }
51878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
519dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    @Override
520dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    FrameLayout getFrameForChild() {
52178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        StackFrame fl = new StackFrame(mContext);
522dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
523dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        return fl;
524dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    }
525dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
52644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
52744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Apply any necessary tranforms for the child that is being added.
52844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
52944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void applyTransformForChildAtIndex(View child, int relativeIndex) {
53044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
53144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
53244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
5339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void dispatchDraw(Canvas canvas) {
534321aa2b0258691c55843dff919eb2705d069167aAdam Cohen        boolean expandClipRegion = false;
535321aa2b0258691c55843dff919eb2705d069167aAdam Cohen
5360ac116b688380489c3690f6f65b282990c221f17Adam Cohen        canvas.getClipBounds(stackInvalidateRect);
537d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        final int childCount = getChildCount();
538d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        for (int i = 0; i < childCount; i++) {
539e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            final View child =  getChildAt(i);
540e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
541e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            if ((lp.horizontalOffset == 0 && lp.verticalOffset == 0) ||
542e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen                    child.getAlpha() == 0f || child.getVisibility() != VISIBLE) {
543e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen                lp.resetInvalidateRect();
544e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            }
545321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            Rect childInvalidateRect = lp.getInvalidateRect();
546321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            if (!childInvalidateRect.isEmpty()) {
547321aa2b0258691c55843dff919eb2705d069167aAdam Cohen                expandClipRegion = true;
548321aa2b0258691c55843dff919eb2705d069167aAdam Cohen                stackInvalidateRect.union(childInvalidateRect);
549321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            }
550321aa2b0258691c55843dff919eb2705d069167aAdam Cohen        }
551321aa2b0258691c55843dff919eb2705d069167aAdam Cohen
552321aa2b0258691c55843dff919eb2705d069167aAdam Cohen        // We only expand the clip bounds if necessary.
553321aa2b0258691c55843dff919eb2705d069167aAdam Cohen        if (expandClipRegion) {
554321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            canvas.save(Canvas.CLIP_SAVE_FLAG);
555321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            canvas.clipRect(stackInvalidateRect, Region.Op.UNION);
556321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            super.dispatchDraw(canvas);
557321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            canvas.restore();
558321aa2b0258691c55843dff919eb2705d069167aAdam Cohen        } else {
559321aa2b0258691c55843dff919eb2705d069167aAdam Cohen            super.dispatchDraw(canvas);
56044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
5619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
56244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void onLayout() {
56444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mFirstLayoutHappened) {
56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mFirstLayoutHappened = true;
56653838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen            updateChildTransforms();
56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
56836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
569c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen        final int newSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
570c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen        if (mSlideAmount != newSlideAmount) {
571c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen            mSlideAmount = newSlideAmount;
572c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * newSlideAmount);
573c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen        }
574c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen
57536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (Float.compare(mPerspectiveShiftY, mNewPerspectiveShiftY) != 0 ||
57636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                Float.compare(mPerspectiveShiftX, mNewPerspectiveShiftX) != 0) {
57778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
57836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mPerspectiveShiftY = mNewPerspectiveShiftY;
57936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mPerspectiveShiftX = mNewPerspectiveShiftX;
58053838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen            updateChildTransforms();
58136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
58244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
58344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
584a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen    @Override
585a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen    public boolean onGenericMotionEvent(MotionEvent event) {
586a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen        if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
587a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen            switch (event.getAction()) {
588a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                case MotionEvent.ACTION_SCROLL: {
589a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                    final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
590a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                    if (vscroll < 0) {
591a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                        pacedScroll(false);
592a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                        return true;
593a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                    } else if (vscroll > 0) {
594a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                        pacedScroll(true);
595a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                        return true;
596a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                    }
597a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                }
598a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen            }
599a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen        }
600a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen        return super.onGenericMotionEvent(event);
601a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen    }
602a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen
603a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen    // This ensures that the frequency of stack flips caused by scrolls is capped
604a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen    private void pacedScroll(boolean up) {
605a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen        long timeSinceLastScroll = System.currentTimeMillis() - mLastScrollTime;
606a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen        if (timeSinceLastScroll > MIN_TIME_BETWEEN_SCROLLS) {
607a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen            if (up) {
608a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                showPrevious();
609a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen            } else {
610a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen                showNext();
611a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen            }
612a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen            mLastScrollTime = System.currentTimeMillis();
613a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen        }
614a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen    }
615a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen
6160e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
6170e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
6180e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
61944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
62044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onInterceptTouchEvent(MotionEvent ev) {
62144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
62244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch(action & MotionEvent.ACTION_MASK) {
62344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_DOWN: {
62444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (mActivePointerId == INVALID_POINTER) {
62544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialX = ev.getX();
62644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialY = ev.getY();
62744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mActivePointerId = ev.getPointerId(0);
62844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
62944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
63044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
63144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
63244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                int pointerIndex = ev.findPointerIndex(mActivePointerId);
63344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (pointerIndex == INVALID_POINTER) {
63444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    // no data for our primary pointer, this shouldn't happen, log it
63544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    Log.d(TAG, "Error: No data for our primary pointer.");
63644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    return false;
63744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
63844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float newY = ev.getY(pointerIndex);
63944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float deltaY = newY - mInitialY;
64044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
64132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
64244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
64344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
64444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
64544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
64644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
64744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
64844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP:
64944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
65044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
65144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
65244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
65344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
65444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
65544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return mSwipeGestureType != GESTURE_NONE;
65644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
65744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
65832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private void beginGestureIfNeeded(float deltaY) {
65932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
660c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            final int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
66132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            cancelLongPress();
66232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            requestDisallowInterceptTouchEvent(true);
66332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
664d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            if (mAdapter == null) return;
665ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen            final int adapterCount = getCount();
666d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
667839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int activeIndex;
668839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
66996d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
670839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
67196d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
672839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
67332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
674c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            boolean endOfStack = mLoopViews && adapterCount == 1 &&
675c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                ((mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_UP) ||
676c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                 (mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_DOWN));
677c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            boolean beginningOfStack = mLoopViews && adapterCount == 1 &&
678c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                ((mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_UP) ||
679c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                 (mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_DOWN));
680c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen
6813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            int stackMode;
682c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            if (mLoopViews && !beginningOfStack && !endOfStack) {
6833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
684c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == -1 || beginningOfStack) {
68596d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex++;
6863352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.BEGINNING_OF_STACK_MODE;
687c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == adapterCount - 1 || endOfStack) {
6883352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.END_OF_STACK_MODE;
6893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else {
6903352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
69132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
6923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE;
6943352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
6953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            View v = getViewAtRelativeIndex(activeIndex);
6963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (v == null) return;
6973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6983352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            setupStackSlider(v, stackMode);
6993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
7003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // We only register this gesture if we've made it this far without a problem
7013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mSwipeGestureType = swipeGestureType;
702a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen            cancelHandleClick();
70332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
70432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
70532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7060e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
7070e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
7080e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
70944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
71044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onTouchEvent(MotionEvent ev) {
711a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen        super.onTouchEvent(ev);
712a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen
71344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
71444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
71544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerIndex == INVALID_POINTER) {
71644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // no data for our primary pointer, this shouldn't happen, log it
71744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            Log.d(TAG, "Error: No data for our primary pointer.");
71844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            return false;
71944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
72044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
72144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
72232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float newX = ev.getX(pointerIndex);
72344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float deltaY = newY - mInitialY;
72432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float deltaX = newX - mInitialX;
72544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker == null) {
72644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = VelocityTracker.obtain();
72744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
72844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mVelocityTracker.addMovement(ev);
72944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
73044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch (action & MotionEvent.ACTION_MASK) {
73144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
73232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
73332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
734dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float rx = deltaX / (mSlideAmount * 1.0f);
73532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
736dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
737839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
73832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(1 - r);
73932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
74032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
74132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
742dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
743839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
74432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(r);
74532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
74632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
74744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
74844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
74944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
75044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP: {
75144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                handlePointerUp(ev);
75244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
75344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
75444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
75544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
75644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
75744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
75844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
75944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
76044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
76144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
76244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
76344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
76444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return true;
76544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
76644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
76744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void onSecondaryPointerUp(MotionEvent ev) {
76844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int activePointerIndex = ev.getActionIndex();
76944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int pointerId = ev.getPointerId(activePointerIndex);
77044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerId == mActivePointerId) {
77144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
77296d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
77344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
77444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            View v = getViewAtRelativeIndex(activeViewIndex);
77544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (v == null) return;
77644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
77744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Our primary pointer has gone up -- let's see if we can find
77844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // another pointer on the view. If so, then we should replace
77944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // our primary pointer with this new pointer and adjust things
78044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // so that the view doesn't jump
78144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            for (int index = 0; index < ev.getPointerCount(); index++) {
78244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (index != activePointerIndex) {
78344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
78444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float x = ev.getX(index);
78544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float y = ev.getY(index);
78644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
787e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
788e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    if (mTouchRect.contains(Math.round(x), Math.round(y))) {
78944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldX = ev.getX(activePointerIndex);
79044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldY = ev.getY(activePointerIndex);
79144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
79244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // adjust our frame of reference to avoid a jump
79344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialY += (y - oldY);
79444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialX += (x - oldX);
79544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
79644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mActivePointerId = ev.getPointerId(index);
79744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        if (mVelocityTracker != null) {
79844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                            mVelocityTracker.clear();
79944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        }
80044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // ok, we're good, we found a new pointer which is touching the active view
80144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        return;
80244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    }
80344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
80444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
80544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
8063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // so end the gesture
80744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            handlePointerUp(ev);
80844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
80944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
81044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
81144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void handlePointerUp(MotionEvent ev) {
81244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
81344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
81444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int deltaY = (int) (newY - mInitialY);
81526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        mLastInteractionTime = System.currentTimeMillis();
81644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
8173d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (mVelocityTracker != null) {
8183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
8193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
8203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
82144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
82244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker != null) {
82344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker.recycle();
82444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = null;
82544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
82644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
8273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
8283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
8290ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
8300ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
8310ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
8320ac116b688380489c3690f6f65b282990c221f17Adam Cohen
83344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe down
834839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
835839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showPrevious();
83696d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
83796d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showNext();
838839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
83932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
8403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
8413d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
8420ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
8430ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
8440ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
8450ac116b688380489c3690f6f65b282990c221f17Adam Cohen
84644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe up
847839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
848839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showNext();
84996d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
85096d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showPrevious();
851839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
852839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
85332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
854839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
85544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe up far enough, snap back down
856839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
857839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
858839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
859839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
860839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
861839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
862839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
86332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
864b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
8652794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress);
8662794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
8672794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
868839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
8692794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
870839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
871839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
87232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
87344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe down far enough, snap back up
874839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
875839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
876839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
877839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
878839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
879839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
880839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
8813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
882b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
8832794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY =
8842794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    PropertyValuesHolder.ofFloat("YProgress",finalYProgress);
8852794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
8862794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
887839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
8882794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
889839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
89044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
89144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
89244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
89344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mSwipeGestureType = GESTURE_NONE;
89432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
89532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
89632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private class StackSlider {
89732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        View mView;
89832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mYProgress;
89932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mXProgress;
90032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int NORMAL_MODE = 0;
9023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int BEGINNING_OF_STACK_MODE = 1;
9033d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int END_OF_STACK_MODE = 2;
9043d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
9053d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        int mMode = NORMAL_MODE;
9063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
907b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider() {
908b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
909b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
910b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider(StackSlider copy) {
911b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mView = copy.mView;
912b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mYProgress = copy.mYProgress;
913b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mXProgress = copy.mXProgress;
9143d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = copy.mMode;
915b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
916b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
91732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float cubic(float r) {
918839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
91932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
92032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
92132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float highlightAlphaInterpolator(float r) {
92232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.4f;
92332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r < pivot) {
924839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(r / pivot);
92532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
926839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
92732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
92832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
92932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
93032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float viewAlphaInterpolator(float r) {
93132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.3f;
93232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r > pivot) {
933839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
93432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
93532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0;
93632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
93732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
93832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
939b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        private float rotationInterpolator(float r) {
940b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            float pivot = 0.2f;
941b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            if (r < pivot) {
942b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return 0;
943b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            } else {
944839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
945b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
946b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
947b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
94832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        void setView(View v) {
94932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mView = v;
95032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
95132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
95232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setYProgress(float r) {
95332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
95432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.min(1.0f, r);
95532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.max(0, r);
95632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
95732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mYProgress = r;
958a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
959a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen
96032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
96132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
96232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
963839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
964839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
965488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            // We need to prevent any clipping issues which may arise by setting a layer type.
966488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            // This doesn't come for free however, so we only want to enable it when required.
967488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            if (Float.compare(0f, mYProgress) != 0 && Float.compare(1.0f, mYProgress) != 0) {
968488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                if (mView.getLayerType() == LAYER_TYPE_NONE) {
969488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                    mView.setLayerType(LAYER_TYPE_HARDWARE, null);
970488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                }
971488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            } else {
972488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                if (mView.getLayerType() != LAYER_TYPE_NONE) {
973488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                    mView.setLayerType(LAYER_TYPE_NONE, null);
974488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                }
975488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            }
976488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen
9773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            switch (mMode) {
9783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case NORMAL_MODE:
979dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
980dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
9813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
9823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
983839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float alpha = viewAlphaInterpolator(1 - r);
9843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
9853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
9863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // so that they don't interfere with click events.
9873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
9883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(VISIBLE);
9893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    } else if (alpha == 0 && mView.getAlpha() != 0
9903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                            && mView.getVisibility() == VISIBLE) {
9913d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(INVISIBLE);
9923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    }
993b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
9943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setAlpha(alpha);
995839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
996839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
9973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
99896d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case END_OF_STACK_MODE:
999839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = r * 0.2f;
1000dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
1001dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
10023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
10033d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
100496d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case BEGINNING_OF_STACK_MODE:
1005839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = (1-r) * 0.2f;
1006dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
1007dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
10083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
10093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
1010b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
101132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
101232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
101332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setXProgress(float r) {
101432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
10153d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.min(2.0f, r);
10163d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.max(-2.0f, r);
101732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
101832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mXProgress = r;
101932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1020a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
102132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
102232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
102332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
10243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r *= 0.2f;
1025dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
1026dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
102732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
102832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
10293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        void setMode(int mode) {
10303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = mode;
10313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
10323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
10333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForNeutralPosition() {
1034839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, 0);
10353d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
10363d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
10373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForOffscreenPosition() {
1038839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, 0);
1039839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1040839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1041839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForNeutralPosition(float velocity) {
1042839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, velocity);
1043839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1044839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1045839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForOffscreenPosition(float velocity) {
1046839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, velocity);
10473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
10483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
1049839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private float getDuration(boolean invert, float velocity) {
10503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mView != null) {
10513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
10523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
1053839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
1054839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        Math.pow(viewLp.verticalOffset, 2));
1055dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
1056dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                        Math.pow(0.4f * mSlideAmount, 2));
1057839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1058839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                if (velocity == 0) {
1059839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
1060839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                } else {
1061839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float duration = invert ? d / Math.abs(velocity) :
1062839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            (maxd - d) / Math.abs(velocity);
1063839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (duration < MINIMUM_ANIMATION_DURATION ||
1064839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            duration > DEFAULT_ANIMATION_DURATION) {
1065839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return getDuration(invert, 0);
1066839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    } else {
1067839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return duration;
1068839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    }
1069839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                }
10703d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            }
10713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return 0;
10723d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
10733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
1074e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
1075e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
1076839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getYProgress() {
107732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mYProgress;
107832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
107932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1080e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
1081e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
1082839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getXProgress() {
108332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mXProgress;
108432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
108544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
108644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    LayoutParams createOrReuseLayoutParams(View v) {
10889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
10899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (currentLp instanceof LayoutParams) {
10909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) currentLp;
10919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setHorizontalOffset(0);
10929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setVerticalOffset(0);
1093839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
1094839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
10959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return lp;
10969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        return new LayoutParams(v);
109832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
109932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
11009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    @Override
11019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1102ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen        checkForAndHandleDataChanged();
11039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final int childCount = getChildCount();
11059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        for (int i = 0; i < childCount; i++) {
11069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            final View child = getChildAt(i);
11079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childRight = mPaddingLeft + child.getMeasuredWidth();
11099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childBottom = mPaddingTop + child.getMeasuredHeight();
11109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
11119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
11139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
11149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
11169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        onLayout();
11179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
11189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
111926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    @Override
112026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    public void advance() {
112126e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime;
1122c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen
1123c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        if (mAdapter == null) return;
1124ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen        final int adapterCount = getCount();
1125c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        if (adapterCount == 1 && mLoopViews) return;
1126c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen
112726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        if (mSwipeGestureType == GESTURE_NONE &&
112826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen                timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) {
112926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen            showNext();
113026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        }
113126e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    }
113226e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
1133839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private void measureChildren() {
1134839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int count = getChildCount();
113536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
113636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int measuredWidth = getMeasuredWidth();
113736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int measuredHeight = getMeasuredHeight();
113836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
113936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int childWidth = Math.round(measuredWidth*(1-PERSPECTIVE_SHIFT_FACTOR_X))
1140026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                - mPaddingLeft - mPaddingRight;
114136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int childHeight = Math.round(measuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR_Y))
1142839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                - mPaddingTop - mPaddingBottom;
1143839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
114436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        int maxWidth = 0;
114536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        int maxHeight = 0;
114636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
1147839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        for (int i = 0; i < count; i++) {
1148839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            final View child = getChildAt(i);
114936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST),
115036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
115136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
115236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            if (child != mHighlight && child != mClickFeedback) {
115336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                final int childMeasuredWidth = child.getMeasuredWidth();
115436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                final int childMeasuredHeight = child.getMeasuredHeight();
115536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                if (childMeasuredWidth > maxWidth) {
115636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    maxWidth = childMeasuredWidth;
115736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                }
115836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                if (childMeasuredHeight > maxHeight) {
115936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    maxHeight = childMeasuredHeight;
116036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                }
116136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            }
116236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
116336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
116436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        mNewPerspectiveShiftX = PERSPECTIVE_SHIFT_FACTOR_X * measuredWidth;
116536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        mNewPerspectiveShiftY = PERSPECTIVE_SHIFT_FACTOR_Y * measuredHeight;
116653838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen
116753838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen        // If we have extra space, we try and spread the items out
116878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (maxWidth > 0 && count > 0 && maxWidth < childWidth) {
116936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mNewPerspectiveShiftX = measuredWidth - maxWidth;
117036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
117136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
117278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (maxHeight > 0 && count > 0 && maxHeight < childHeight) {
117336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mNewPerspectiveShiftY = measuredHeight - maxHeight;
1174839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1175839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
1176839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1177839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    @Override
1178839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1179839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
1180839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
1181839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
1182839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
1183839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1184839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
1185839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1186839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // We need to deal with the case where our parent hasn't told us how
1187839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // big we should be. In this case we should
1188026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorY = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_Y);
1189839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
1190839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ?
1191026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildHeight * (1 + factorY)) +
1192839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingTop + mPaddingBottom : 0;
1193839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
1194189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
1195026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                int height = Math.round(mReferenceChildHeight * (1 + factorY))
1196189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                        + mPaddingTop + mPaddingBottom;
1197189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (height <= heightSpecSize) {
1198189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize = height;
1199189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
1200189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize |= MEASURED_STATE_TOO_SMALL;
1201ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen
1202189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
1203189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
1204189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                heightSpecSize = 0;
1205189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
1206839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1207839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1208026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorX = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_X);
1209839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
1210026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            widthSpecSize = haveChildRefSize ?
1211026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildWidth * (1 + factorX)) +
1212026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    mPaddingLeft + mPaddingRight : 0;
1213839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
1214189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
1215189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
1216189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (width <= widthSpecSize) {
1217189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize = width;
1218189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
1219189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize |= MEASURED_STATE_TOO_SMALL;
1220189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
1221189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
1222189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                widthSpecSize = 0;
1223189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
1224839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1225839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        setMeasuredDimension(widthSpecSize, heightSpecSize);
1226839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        measureChildren();
1227839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
1228839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
12298a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    @Override
12308a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
12318a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        super.onInitializeAccessibilityEvent(event);
12328a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        event.setClassName(StackView.class.getName());
12338a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    }
12348a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov
12358a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    @Override
12368a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
12378a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        super.onInitializeAccessibilityNodeInfo(info);
12388a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        info.setClassName(StackView.class.getName());
123948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov        info.setScrollable(getChildCount() > 1);
1240fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov        if (isEnabled()) {
1241fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov            if (getDisplayedChild() < getChildCount() - 1) {
1242fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1243fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov            }
1244fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov            if (getDisplayedChild() > 0) {
1245fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1246fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov            }
124748d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov        }
124848d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov    }
124948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov
125048d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov    @Override
125148d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov    public boolean performAccessibilityAction(int action, Bundle arguments) {
125248d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov        if (super.performAccessibilityAction(action, arguments)) {
125348d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov            return true;
125448d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov        }
1255fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov        if (!isEnabled()) {
1256fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov            return false;
1257fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov        }
125848d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov        switch (action) {
125948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
126048d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                if (getDisplayedChild() < getChildCount() - 1) {
126148d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                    showNext();
126248d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                    return true;
126348d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                }
126448d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov            } return false;
126548d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
126648d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                if (getDisplayedChild() > 0) {
126748d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                    showPrevious();
126848d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                    return true;
126948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov                }
127048d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov            } return false;
127148d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov        }
127248d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov        return false;
12738a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    }
12748a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov
12759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    class LayoutParams extends ViewGroup.LayoutParams {
12769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int horizontalOffset;
12779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int verticalOffset;
12789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        View mView;
1279d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect parentRect = new Rect();
1280d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect invalidateRect = new Rect();
1281d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final RectF invalidateRectf = new RectF();
1282d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect globalInvalidateRect = new Rect();
12839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(View view) {
12859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(0, 0);
1286839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1287839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
12889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
12899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
12909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mView = view;
12919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
12929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(Context c, AttributeSet attrs) {
12949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(c, attrs);
12959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
12969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
1297839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1298839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
1299b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
1300b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
13019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void invalidateGlobalRegion(View v, Rect r) {
1302d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            // We need to make a new rect here, so as not to modify the one passed
1303d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            globalInvalidateRect.set(r);
13041ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen            globalInvalidateRect.union(0, 0, getWidth(), getHeight());
13059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View p = v;
13069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
13079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            boolean firstPass = true;
13099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            parentRect.set(0, 0, 0, 0);
1310b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen            while (p.getParent() != null && p.getParent() instanceof View
1311d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    && !parentRect.contains(globalInvalidateRect)) {
13129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (!firstPass) {
1313d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
1314d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                            - p.getScrollY());
13159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                }
13169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                firstPass = false;
13179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                p = (View) p.getParent();
1318b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                parentRect.set(p.getScrollX(), p.getScrollY(),
13191ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen                        p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
13201ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen                p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top,
13211ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen                        globalInvalidateRect.right, globalInvalidateRect.bottom);
13229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
13239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1324d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top,
1325d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.right, globalInvalidateRect.bottom);
1326d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
1327d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
1328d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        Rect getInvalidateRect() {
1329d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            return invalidateRect;
1330d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
13319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1332d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        void resetInvalidateRect() {
13330ac116b688380489c3690f6f65b282990c221f17Adam Cohen            invalidateRect.set(0, 0, 0, 0);
13349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
133532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1336a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase        // This is public so that ObjectAnimator can access it
13379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setVerticalOffset(int newVerticalOffset) {
13380ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(horizontalOffset, newVerticalOffset);
13399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
13409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setHorizontalOffset(int newHorizontalOffset) {
13420ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(newHorizontalOffset, verticalOffset);
13430ac116b688380489c3690f6f65b282990c221f17Adam Cohen        }
13440ac116b688380489c3690f6f65b282990c221f17Adam Cohen
13450ac116b688380489c3690f6f65b282990c221f17Adam Cohen        public void setOffsets(int newHorizontalOffset, int newVerticalOffset) {
13460ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset;
13479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = newHorizontalOffset;
13480ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int verticalOffsetDelta = newVerticalOffset - verticalOffset;
13490ac116b688380489c3690f6f65b282990c221f17Adam Cohen            verticalOffset = newVerticalOffset;
13509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
13529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
13530ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft());
13540ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight());
13550ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop());
13560ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom());
13570ac116b688380489c3690f6f65b282990c221f17Adam Cohen
13580ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRectf.set(left, top, right, bottom);
13599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
13619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
13629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
13639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
13649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
13659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13660ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
13679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
13689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
13699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
13709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
13729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
13739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
137432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
137532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
13769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static class HolographicHelper {
13779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mHolographicPaint = new Paint();
13789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mErasePaint = new Paint();
1379839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private final Paint mBlurPaint = new Paint();
13808baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int RES_OUT = 0;
13818baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int CLICK_FEEDBACK = 1;
13828baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private float mDensity;
1383e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mSmallBlurMaskFilter;
1384e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mLargeBlurMaskFilter;
1385e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mCanvas = new Canvas();
1386e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mMaskCanvas = new Canvas();
1387e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final int[] mTmpXY = new int[2];
1388e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Matrix mIdentityMatrix = new Matrix();
138932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1390dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        HolographicHelper(Context context) {
13918baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mDensity = context.getResources().getDisplayMetrics().density;
1392dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
13939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setFilterBitmap(true);
1394839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
13959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
13969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setFilterBitmap(true);
1397e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
1398e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL);
1399e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL);
14009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
14019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
140226f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        Bitmap createClickOutline(View v, int color) {
140326f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen            return createOutline(v, CLICK_FEEDBACK, color);
140426f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        }
140526f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen
140626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        Bitmap createResOutline(View v, int color) {
140726f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen            return createOutline(v, RES_OUT, color);
14088baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
14098baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
141026f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen        Bitmap createOutline(View v, int type, int color) {
141126f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen            mHolographicPaint.setColor(color);
14128baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (type == RES_OUT) {
1413e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mSmallBlurMaskFilter);
14148baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            } else if (type == CLICK_FEEDBACK) {
1415e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mLargeBlurMaskFilter);
14168baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
14178baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
14189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
14199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                return null;
14209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
14219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1422dde331cebd87982faded6818ad5f9927ff994c96Dianne Hackborn            Bitmap bitmap = Bitmap.createBitmap(v.getResources().getDisplayMetrics(),
1423dde331cebd87982faded6818ad5f9927ff994c96Dianne Hackborn                    v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888);
1424e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mCanvas.setBitmap(bitmap);
142532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
14269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float rotationX = v.getRotationX();
1427839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float rotation = v.getRotation();
1428839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float translationY = v.getTranslationY();
1429026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            float translationX = v.getTranslationX();
14309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(0);
1431839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(0);
1432839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(0);
1433026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(0);
1434e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            v.draw(mCanvas);
14359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(rotationX);
1436839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(rotation);
1437839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(translationY);
1438026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(translationX);
14399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1440e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            drawOutline(mCanvas, bitmap);
14416311d0a079702b29984c0d31937345be105e1a5eDianne Hackborn            mCanvas.setBitmap(null);
14429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return bitmap;
14439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
14449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
14459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void drawOutline(Canvas dest, Bitmap src) {
1446e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            final int[] xy = mTmpXY;
1447839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
1448e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.setBitmap(mask);
1449e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
14509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawColor(0, PorterDuff.Mode.CLEAR);
1451e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            dest.setMatrix(mIdentityMatrix);
1452839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
14536311d0a079702b29984c0d31937345be105e1a5eDianne Hackborn            mMaskCanvas.setBitmap(null);
14549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mask.recycle();
14559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
145632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
1457ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen}
1458