StackView.java revision 53838d265e36fb50341af168f5a91c3341c3cfc3
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;
2332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap;
24839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter;
2532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas;
2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix;
2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint;
2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff;
2932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode;
3044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect;
319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF;
32d51bbb5b56446519db88f49f2682da782b0069acAdam Cohenimport android.graphics.Region;
33839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter;
3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet;
3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log;
3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent;
3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker;
3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View;
3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration;
4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup;
41b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator;
4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView;
4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView
4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/**
4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe
4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children.
4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator {
5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final String TAG = "StackView";
5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Default animation parameters
5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
55e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int DEFAULT_ANIMATION_DURATION = 400;
56026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final int FADE_IN_ANIMATION_DURATION = 800;
57e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int MINIMUM_ANIMATION_DURATION = 50;
58c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen    private static final int STACK_RELAYOUT_DURATION = 100;
5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
61839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Parameters effecting the perspective visuals
62839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
63026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_Y = 0.1f;
64026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_X = 0.1f;
65026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
6636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mPerspectiveShiftX;
6736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mPerspectiveShiftY;
6836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mNewPerspectiveShiftX;
6936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mNewPerspectiveShiftY;
7036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
71e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    @SuppressWarnings({"FieldCanBeLocal"})
7278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    private static final float PERSPECTIVE_SCALE_FACTOR = 0f;
73839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
74839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
75839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Represent the two possible stack modes, one where items slide up, and the other
76839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * where items slide down. The perspective is also inverted between these two modes.
77839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
78839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_UP = 0;
79839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_DOWN = 1;
80839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
81839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These specify the different gesture states
8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
845b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_NONE = 0;
855b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_UP = 1;
865b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_DOWN = 2;
8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Specifies how far you need to swipe (up or down) before it
9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * will be consider a completed gesture when you lift your finger
9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
92a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    private static final float SWIPE_THRESHOLD_RATIO = 0.2f;
93a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen
94a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    /**
95a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * Specifies the total distance, relative to the size of the stack,
96a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * that views will be slid, either up or down
97a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     */
985b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SLIDE_UP_RATIO = 0.7f;
9944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Sentinel value for no current active pointer.
10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Used by {@link #mActivePointerId}.
10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private static final int INVALID_POINTER = -1;
10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
107839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
108839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
109839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int NUM_ACTIVE_VIEWS = 5;
110839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
111dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private static final int FRAME_PADDING = 4;
112839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
113e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy    private final Rect mTouchRect = new Rect();
114e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
11526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000;
11626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
117839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These variables are all related to the current state of touch interaction
11944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * with the stack
12044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
12144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialY;
12244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialX;
12344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mActivePointerId;
12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mYVelocity = 0;
12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeGestureType = GESTURE_NONE;
126dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mSlideAmount;
12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeThreshold;
12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mTouchSlop;
12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mMaximumVelocity;
13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private VelocityTracker mVelocityTracker;
1313352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private boolean mTransitionIsSetup = false;
13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static HolographicHelper sHolographicHelper;
13432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private ImageView mHighlight;
1358baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private ImageView mClickFeedback;
1368baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private boolean mClickFeedbackIsValid = false;
13732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private StackSlider mStackSlider;
13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private boolean mFirstLayoutHappened = false;
13926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private long mLastInteractionTime = 0;
140839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private int mStackMode;
141dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mFramePadding;
1420ac116b688380489c3690f6f65b282990c221f17Adam Cohen    private final Rect stackInvalidateRect = new Rect();
14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context) {
14544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context);
14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
14944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context, AttributeSet attrs) {
15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context, attrs);
15144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
15244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void initStackView() {
15596d8d56302da81b24333b204e6d7f15064538036Adam Cohen        configureViewAnimator(NUM_ACTIVE_VIEWS, 1);
15644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        setStaticTransformationsEnabled(true);
15744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
158b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        mTouchSlop = configuration.getScaledTouchSlop();
15944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
16044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
16132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
16232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight = new ImageView(getContext());
16332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
16432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
1658baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
1668baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback = new ImageView(getContext());
1678baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback));
1688baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback));
1698baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
1708baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
17132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mStackSlider = new StackSlider();
17232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (sHolographicHelper == null) {
174dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            sHolographicHelper = new HolographicHelper(mContext);
17532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
1769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipChildren(false);
1779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipToPadding(false);
1781480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen
179839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // This sets the form of the StackView, which is currently to have the perspective-shifted
180839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // views above the active view, and have items slide down when sliding out. The opposite is
181839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // available by using ITEMS_SLIDE_UP.
182839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        mStackMode = ITEMS_SLIDE_DOWN;
183839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1841480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // This is a flag to indicate the the stack is loading for the first time
1851480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        mWhichChild = -1;
186dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
187dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // Adjust the frame padding based on the density, since the highlight changes based
188dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // on the density
189dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        final float density = mContext.getResources().getDisplayMetrics().density;
190dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
19144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
19244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
19678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    void transformViewForTransition(int fromIndex, int toIndex, final View view, boolean animate) {
19769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        ObjectAnimator alphaOa = null;
19869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        ObjectAnimator oldAlphaOa = null;
19969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
20078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (!animate) {
20178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelSliderAnimator();
20278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            view.setRotationX(0f);
20378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
20478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setVerticalOffset(0);
20578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setHorizontalOffset(0);
20669d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        }
20769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
208502045849f760f018f662d752020aef32d4cecd9Adam Cohen        if (fromIndex == -1 && toIndex == getNumActiveViews() -1) {
20944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item in
21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (view.getAlpha() == 1) {
21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                view.setAlpha(0);
21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
213502045849f760f018f662d752020aef32d4cecd9Adam Cohen            transformViewAtIndex(toIndex, view, false);
214b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(VISIBLE);
215b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
21678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelAlphaAnimator();
21778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (animate) {
21878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f);
21978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                alphaOa.setDuration(FADE_IN_ANIMATION_DURATION);
22078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setAlphaAnimator(alphaOa);
22178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                alphaOa.start();
22278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            } else {
22378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                view.setAlpha(1.0f);
22478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
22596d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 0 && toIndex == 1) {
22644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item in
22778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelSliderAnimator();
22844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setVisibility(VISIBLE);
22932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
230839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
231b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
23269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            animationSlider.setView(view);
23378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
23478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (animate) {
23578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f);
23678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
23778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator slideIn = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
23878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                        slideInX, slideInY);
23978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideIn.setDuration(duration);
24078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideIn.setInterpolator(new LinearInterpolator());
24178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setSliderAnimator(slideIn);
24278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideIn.start();
24378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            } else {
24478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setYProgress(0f);
24578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setXProgress(0f);
24678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
24796d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 1 && toIndex == 0) {
24844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item out
24978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelSliderAnimator();
250839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
25144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
252b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
25369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            animationSlider.setView(view);
25478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (animate) {
25578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f);
25678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
25778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator slideOut = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
25878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                        slideOutX, slideOutY);
25978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideOut.setDuration(duration);
26078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideOut.setInterpolator(new LinearInterpolator());
26178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setSliderAnimator(slideOut);
26278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                slideOut.start();
26378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            } else {
26478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setYProgress(1.0f);
26578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                animationSlider.setXProgress(0f);
26678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
26769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        } else if (toIndex == 0) {
26844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Make sure this view that is "waiting in the wings" is invisible
26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setAlpha(0.0f);
270b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(INVISIBLE);
27178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        } else if ((fromIndex == 0 || fromIndex == 1) && toIndex > 1) {
27269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setVisibility(VISIBLE);
27369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setAlpha(1.0f);
27478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            view.setRotationX(0f);
27578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
27678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setVerticalOffset(0);
27778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            lp.setHorizontalOffset(0);
278c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen        } else if (fromIndex == -1) {
279c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setAlpha(1.0f);
280c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setVisibility(VISIBLE);
28144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (toIndex == -1) {
28244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item out
28378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelAlphaAnimator();
28478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (animate) {
28578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f);
28678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                alphaOa.setDuration(STACK_RELAYOUT_DURATION);
28778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setAlphaAnimator(alphaOa);
28878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                alphaOa.start();
28978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            } else {
29078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                view.setAlpha(0f);
29178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
29244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
293839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
294839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // Implement the faked perspective
295839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (toIndex != -1) {
29678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            transformViewAtIndex(toIndex, view, animate);
297f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        }
298f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    }
299839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
30036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private void transformViewAtIndex(int index, final View view, boolean animate) {
30136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float maxPerspectiveShiftY = mPerspectiveShiftY;
30236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float maxPerspectiveShiftX = mPerspectiveShiftX;
303026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
304c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        if (mStackMode == ITEMS_SLIDE_DOWN) {
305c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            index = mMaxNumActiveViews - index - 1;
306c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            if (index == mMaxNumActiveViews - 1) index--;
307c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        } else {
308c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            index--;
309c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            if (index < 0) index++;
310c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        }
311026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
312026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float r = (index * 1.0f) / (mMaxNumActiveViews - 2);
313026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
31436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
315026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
316c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        float perspectiveTranslationY = r * maxPerspectiveShiftY;
317c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        float scaleShiftCorrectionY = (scale - 1) *
318026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR_Y) / 2.0f);
31936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float transY = perspectiveTranslationY + scaleShiftCorrectionY;
320026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
321026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float perspectiveTranslationX = (1 - r) * maxPerspectiveShiftX;
322026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float scaleShiftCorrectionX =  (1 - scale) *
323026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredWidth() * (1 - PERSPECTIVE_SHIFT_FACTOR_X) / 2.0f);
32436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float transX = perspectiveTranslationX + scaleShiftCorrectionX;
32536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
326c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        // If this view is currently being animated for a certain position, we need to cancel
32769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        // this animation so as not to interfere with the new transformation.
32878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (view instanceof StackFrame) {
32978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            ((StackFrame) view).cancelTransformAnimator();
33069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        }
33169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
33236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (animate) {
33336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", transX);
33436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY);
33536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder scalePropX = PropertyValuesHolder.ofFloat("scaleX", scale);
33636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder scalePropY = PropertyValuesHolder.ofFloat("scaleY", scale);
33736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
33836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(view, scalePropX, scalePropY,
33936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    translationY, translationX);
340c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            oa.setDuration(STACK_RELAYOUT_DURATION);
34178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (view instanceof StackFrame) {
34278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ((StackFrame) view).setTransformAnimator(oa);
34378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
34436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            oa.start();
34536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        } else {
34636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setTranslationX(transX);
34736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setTranslationY(transY);
34836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setScaleX(scale);
34936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setScaleY(scale);
35036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
351026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    }
352026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
3533352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private void setupStackSlider(View v, int mode) {
3543352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mStackSlider.setMode(mode);
3553352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (v != null) {
3563352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
3573352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setRotation(v.getRotation());
3583352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setTranslationY(v.getTranslationY());
359026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            mHighlight.setTranslationX(v.getTranslationX());
3603352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.bringToFront();
3613352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.bringToFront();
3623352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mStackSlider.setView(v);
3633352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3643352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.setVisibility(VISIBLE);
3653352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3663352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3673352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3680e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
3690e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
3700e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
3713352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
3723352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
3733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showNext() {
3740ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
3753352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
3763352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(1);
3773352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
3783352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
3793352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(0);
3803352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
3813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
3823352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showNext();
3843352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3853352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3860e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
3870e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
3880e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
3893352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
3903352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
3913352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showPrevious() {
3920ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
3933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
3943352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(0);
3953352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
3963352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
3973352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(1);
3983352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
3993352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
4003352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
4013352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showPrevious();
4023352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
4033352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
40496d8d56302da81b24333b204e6d7f15064538036Adam Cohen    @Override
405ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen    void showOnly(int childIndex, boolean animate) {
406ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen        super.showOnly(childIndex, animate);
40796d8d56302da81b24333b204e6d7f15064538036Adam Cohen
40896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        // Here we need to make sure that the z-order of the children is correct
4093352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
41096d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int index = modulo(i, getWindowSize());
4110ac116b688380489c3690f6f65b282990c221f17Adam Cohen            ViewAndIndex vi = mViewsMap.get(index);
4120ac116b688380489c3690f6f65b282990c221f17Adam Cohen            if (vi != null) {
4130ac116b688380489c3690f6f65b282990c221f17Adam Cohen                View v = mViewsMap.get(index).view;
4140ac116b688380489c3690f6f65b282990c221f17Adam Cohen                if (v != null) v.bringToFront();
4150ac116b688380489c3690f6f65b282990c221f17Adam Cohen            }
41696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        }
41778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (mHighlight != null) {
41878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            mHighlight.bringToFront();
41978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
4203352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mTransitionIsSetup = false;
4218baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedbackIsValid = false;
4228baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4238baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4248baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void updateClickFeedback() {
4258baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        if (!mClickFeedbackIsValid) {
4269c295482dd739e80dd49ea0dd3102ad6be6742ddAdam Cohen            View v = getViewAtRelativeIndex(1);
4278baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (v != null) {
4288baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v,
4298baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                        HolographicHelper.CLICK_FEEDBACK));
4308baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationX(v.getTranslationX());
4318baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationY(v.getTranslationY());
4328baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
4338baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mClickFeedbackIsValid = true;
4348baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
4358baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4368baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4378baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
4388baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void showTapFeedback(View v) {
4398baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        updateClickFeedback();
4408baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(VISIBLE);
4418baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.bringToFront();
4428baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
4438baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4458baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
4468baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void hideTapFeedback(View v) {
4478baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
4488baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
44996d8d56302da81b24333b204e6d7f15064538036Adam Cohen    }
45096d8d56302da81b24333b204e6d7f15064538036Adam Cohen
451f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void updateChildTransforms() {
45296d8d56302da81b24333b204e6d7f15064538036Adam Cohen        for (int i = 0; i < getNumActiveViews(); i++) {
453f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            View v = getViewAtRelativeIndex(i);
454f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            if (v != null) {
45536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                transformViewAtIndex(i, v, false);
456f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            }
457839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
45944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
46078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    private static class StackFrame extends FrameLayout {
46178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        WeakReference<ObjectAnimator> alphaAnimator;
46278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        WeakReference<ObjectAnimator> transformAnimator;
46378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        WeakReference<ObjectAnimator> sliderAnimator;
46478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
46578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        public StackFrame(Context context) {
46678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            super(context);
46778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
46878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
46978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        void setAlphaAnimator(ObjectAnimator oa) {
47078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            alphaAnimator = new WeakReference<ObjectAnimator>(oa);
47178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
47278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
47378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        void setTransformAnimator(ObjectAnimator oa) {
47478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            transformAnimator = new WeakReference<ObjectAnimator>(oa);
47578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
47678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
47778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        void setSliderAnimator(ObjectAnimator oa) {
47878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            sliderAnimator = new WeakReference<ObjectAnimator>(oa);
47978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
48078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
48178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        boolean cancelAlphaAnimator() {
48278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (alphaAnimator != null) {
48378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator oa = alphaAnimator.get();
48478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                if (oa != null) {
48578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    oa.cancel();
48678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    return true;
48778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                }
48878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
48978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            return false;
49078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
49178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
49278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        boolean cancelTransformAnimator() {
49378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (transformAnimator != null) {
49478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator oa = transformAnimator.get();
49578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                if (oa != null) {
49678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    oa.cancel();
49778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    return true;
49878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                }
49978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
50078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            return false;
50178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
50278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
50378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        boolean cancelSliderAnimator() {
50478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            if (sliderAnimator != null) {
50578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                ObjectAnimator oa = sliderAnimator.get();
50678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                if (oa != null) {
50778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    oa.cancel();
50878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                    return true;
50978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen                }
51078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            }
51178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen            return false;
51278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        }
51378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen    }
51478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
515dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    @Override
516dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    FrameLayout getFrameForChild() {
51778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        StackFrame fl = new StackFrame(mContext);
518dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
519dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        return fl;
520dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    }
521dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
52244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
52344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Apply any necessary tranforms for the child that is being added.
52444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
52544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void applyTransformForChildAtIndex(View child, int relativeIndex) {
52644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
52744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
52844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
5299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void dispatchDraw(Canvas canvas) {
5300ac116b688380489c3690f6f65b282990c221f17Adam Cohen        canvas.getClipBounds(stackInvalidateRect);
531d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        final int childCount = getChildCount();
532d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        for (int i = 0; i < childCount; i++) {
533e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            final View child =  getChildAt(i);
534e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
535e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            if ((lp.horizontalOffset == 0 && lp.verticalOffset == 0) ||
536e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen                    child.getAlpha() == 0f || child.getVisibility() != VISIBLE) {
537e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen                lp.resetInvalidateRect();
538e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen            }
5390ac116b688380489c3690f6f65b282990c221f17Adam Cohen            stackInvalidateRect.union(lp.getInvalidateRect());
54044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
541d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.save(Canvas.CLIP_SAVE_FLAG);
5420ac116b688380489c3690f6f65b282990c221f17Adam Cohen        canvas.clipRect(stackInvalidateRect, Region.Op.UNION);
543d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        super.dispatchDraw(canvas);
544d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.restore();
5459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
54644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void onLayout() {
54844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mFirstLayoutHappened) {
549dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
550dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount);
55144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mFirstLayoutHappened = true;
55253838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen            updateChildTransforms();
55344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
55436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
55536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (Float.compare(mPerspectiveShiftY, mNewPerspectiveShiftY) != 0 ||
55636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                Float.compare(mPerspectiveShiftX, mNewPerspectiveShiftX) != 0) {
55778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen
55836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mPerspectiveShiftY = mNewPerspectiveShiftY;
55936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mPerspectiveShiftX = mNewPerspectiveShiftX;
56053838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen            updateChildTransforms();
56136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
56244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
56344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5640e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
5650e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
5660e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
56844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onInterceptTouchEvent(MotionEvent ev) {
56944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
57044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch(action & MotionEvent.ACTION_MASK) {
57144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_DOWN: {
57244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (mActivePointerId == INVALID_POINTER) {
57344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialX = ev.getX();
57444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialY = ev.getY();
57544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mActivePointerId = ev.getPointerId(0);
57644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
57744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
57844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
57944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
58044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                int pointerIndex = ev.findPointerIndex(mActivePointerId);
58144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (pointerIndex == INVALID_POINTER) {
58244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    // no data for our primary pointer, this shouldn't happen, log it
58344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    Log.d(TAG, "Error: No data for our primary pointer.");
58444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    return false;
58544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
58644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float newY = ev.getY(pointerIndex);
58744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float deltaY = newY - mInitialY;
58844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
58932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
59044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
59144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
59244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
59344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
59444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
59544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
59644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP:
59744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
59844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
59944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
60044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
60144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
60244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
60344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return mSwipeGestureType != GESTURE_NONE;
60444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
60544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
60632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private void beginGestureIfNeeded(float deltaY) {
60732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
608c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            final int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
60932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            cancelLongPress();
61032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            requestDisallowInterceptTouchEvent(true);
61132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
612d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            if (mAdapter == null) return;
613ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen            final int adapterCount = getCount();
614d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
615839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int activeIndex;
616839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
61796d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
618839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
61996d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
620839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
62132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
622c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            boolean endOfStack = mLoopViews && adapterCount == 1 &&
623c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                ((mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_UP) ||
624c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                 (mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_DOWN));
625c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            boolean beginningOfStack = mLoopViews && adapterCount == 1 &&
626c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                ((mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_UP) ||
627c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen                 (mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_DOWN));
628c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen
6293352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            int stackMode;
630c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            if (mLoopViews && !beginningOfStack && !endOfStack) {
6313352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
632c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == -1 || beginningOfStack) {
63396d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex++;
6343352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.BEGINNING_OF_STACK_MODE;
635c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == adapterCount - 1 || endOfStack) {
6363352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.END_OF_STACK_MODE;
6373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else {
6383352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
63932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
6403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6413352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE;
6423352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
6433d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            View v = getViewAtRelativeIndex(activeIndex);
6443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (v == null) return;
6453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6463352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            setupStackSlider(v, stackMode);
6473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // We only register this gesture if we've made it this far without a problem
6493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mSwipeGestureType = swipeGestureType;
650a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen            cancelHandleClick();
65132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
65232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
65332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6540e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen    /**
6550e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     * {@inheritDoc}
6560e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen     */
65744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
65844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onTouchEvent(MotionEvent ev) {
659a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen        super.onTouchEvent(ev);
660a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen
66144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
66244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
66344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerIndex == INVALID_POINTER) {
66444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // no data for our primary pointer, this shouldn't happen, log it
66544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            Log.d(TAG, "Error: No data for our primary pointer.");
66644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            return false;
66744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
66844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
66944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
67032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float newX = ev.getX(pointerIndex);
67144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float deltaY = newY - mInitialY;
67232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float deltaX = newX - mInitialX;
67344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker == null) {
67444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = VelocityTracker.obtain();
67544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
67644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mVelocityTracker.addMovement(ev);
67744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
67844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch (action & MotionEvent.ACTION_MASK) {
67944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
68032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
68132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
682dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float rx = deltaX / (mSlideAmount * 1.0f);
68332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
684dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
685839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
68632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(1 - r);
68732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
68832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
68932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
690dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
691839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
69232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(r);
69332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
69432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
69544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
69644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
69744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
69844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP: {
69944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                handlePointerUp(ev);
70044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
70144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
70244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
70344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
70444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
70544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
70644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
70744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
70844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
70944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
71044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
71144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
71244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return true;
71344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
71444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
71544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void onSecondaryPointerUp(MotionEvent ev) {
71644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int activePointerIndex = ev.getActionIndex();
71744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int pointerId = ev.getPointerId(activePointerIndex);
71844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerId == mActivePointerId) {
71944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
72096d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
72144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
72244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            View v = getViewAtRelativeIndex(activeViewIndex);
72344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (v == null) return;
72444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
72544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Our primary pointer has gone up -- let's see if we can find
72644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // another pointer on the view. If so, then we should replace
72744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // our primary pointer with this new pointer and adjust things
72844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // so that the view doesn't jump
72944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            for (int index = 0; index < ev.getPointerCount(); index++) {
73044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (index != activePointerIndex) {
73144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
73244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float x = ev.getX(index);
73344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float y = ev.getY(index);
73444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
735e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
736e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    if (mTouchRect.contains(Math.round(x), Math.round(y))) {
73744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldX = ev.getX(activePointerIndex);
73844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldY = ev.getY(activePointerIndex);
73944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
74044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // adjust our frame of reference to avoid a jump
74144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialY += (y - oldY);
74244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialX += (x - oldX);
74344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
74444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mActivePointerId = ev.getPointerId(index);
74544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        if (mVelocityTracker != null) {
74644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                            mVelocityTracker.clear();
74744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        }
74844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // ok, we're good, we found a new pointer which is touching the active view
74944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        return;
75044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    }
75144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
75244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
75344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
7543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // so end the gesture
75544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            handlePointerUp(ev);
75644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
75744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
75844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
75944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void handlePointerUp(MotionEvent ev) {
76044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
76144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
76244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int deltaY = (int) (newY - mInitialY);
76326e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        mLastInteractionTime = System.currentTimeMillis();
76444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
7653d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (mVelocityTracker != null) {
7663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
7673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
7683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
76944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
77044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker != null) {
77144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker.recycle();
77244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = null;
77344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
77444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
7753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
7763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
7770ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
7780ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
7790ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
7800ac116b688380489c3690f6f65b282990c221f17Adam Cohen
78144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe down
782839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
783839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showPrevious();
78496d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
78596d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showNext();
786839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
78732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
7883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
7893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
7900ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
7910ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
7920ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
7930ac116b688380489c3690f6f65b282990c221f17Adam Cohen
79444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe up
795839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
796839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showNext();
79796d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
79896d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showPrevious();
799839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
800839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
80132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
802839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
80344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe up far enough, snap back down
804839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
805839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
806839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
807839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
808839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
809839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
810839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
81132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
812b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
8132794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress);
8142794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
8152794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
816839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
8172794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
818839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
819839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
82032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
82144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe down far enough, snap back up
822839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
823839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
824839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
825839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
826839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
827839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
828839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
8293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
830b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
8312794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY =
8322794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    PropertyValuesHolder.ofFloat("YProgress",finalYProgress);
8332794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
8342794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
835839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
8362794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
837839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
83844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
83944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
84044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
84144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mSwipeGestureType = GESTURE_NONE;
84232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
84332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
84432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private class StackSlider {
84532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        View mView;
84632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mYProgress;
84732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mXProgress;
84832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int NORMAL_MODE = 0;
8503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int BEGINNING_OF_STACK_MODE = 1;
8513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int END_OF_STACK_MODE = 2;
8523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
8533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        int mMode = NORMAL_MODE;
8543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
855b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider() {
856b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
857b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
858b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider(StackSlider copy) {
859b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mView = copy.mView;
860b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mYProgress = copy.mYProgress;
861b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mXProgress = copy.mXProgress;
8623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = copy.mMode;
863b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
864b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
86532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float cubic(float r) {
866839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
86732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
86832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
86932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float highlightAlphaInterpolator(float r) {
87032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.4f;
87132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r < pivot) {
872839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(r / pivot);
87332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
874839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
87532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
87632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
87732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
87832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float viewAlphaInterpolator(float r) {
87932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.3f;
88032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r > pivot) {
881839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
88232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
88332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0;
88432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
88532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
88632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
887b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        private float rotationInterpolator(float r) {
888b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            float pivot = 0.2f;
889b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            if (r < pivot) {
890b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return 0;
891b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            } else {
892839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
893b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
894b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
895b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
89632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        void setView(View v) {
89732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mView = v;
89832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
89932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
90032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setYProgress(float r) {
90132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
90232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.min(1.0f, r);
90332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.max(0, r);
90432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
90532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mYProgress = r;
906a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
907a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen
90832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
90932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
91032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
911839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
912839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
913488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            // We need to prevent any clipping issues which may arise by setting a layer type.
914488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            // This doesn't come for free however, so we only want to enable it when required.
915488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            if (Float.compare(0f, mYProgress) != 0 && Float.compare(1.0f, mYProgress) != 0) {
916488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                if (mView.getLayerType() == LAYER_TYPE_NONE) {
917488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                    mView.setLayerType(LAYER_TYPE_HARDWARE, null);
918488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                }
919488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            } else {
920488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                if (mView.getLayerType() != LAYER_TYPE_NONE) {
921488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                    mView.setLayerType(LAYER_TYPE_NONE, null);
922488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                }
923488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            }
924488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen
9253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            switch (mMode) {
9263d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case NORMAL_MODE:
927dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
928dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
9293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
9303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
931839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float alpha = viewAlphaInterpolator(1 - r);
9323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
9333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
9343d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // so that they don't interfere with click events.
9353d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
9363d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(VISIBLE);
9373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    } else if (alpha == 0 && mView.getAlpha() != 0
9383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                            && mView.getVisibility() == VISIBLE) {
9393d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(INVISIBLE);
9403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    }
941b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
9423d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setAlpha(alpha);
943839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
944839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
9453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
94696d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case END_OF_STACK_MODE:
947839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = r * 0.2f;
948dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
949dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
9503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
9513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
95296d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case BEGINNING_OF_STACK_MODE:
953839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = (1-r) * 0.2f;
954dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
955dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
9563d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
9573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
958b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
95932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
96032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
96132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setXProgress(float r) {
96232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
9633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.min(2.0f, r);
9643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.max(-2.0f, r);
96532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
96632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mXProgress = r;
96732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
968a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
96932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
97032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
97132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9723d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r *= 0.2f;
973dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
974dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
97532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
97632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        void setMode(int mode) {
9783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = mode;
9793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
9803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
9813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForNeutralPosition() {
982839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, 0);
9833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
9843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
9853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForOffscreenPosition() {
986839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, 0);
987839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
988839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
989839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForNeutralPosition(float velocity) {
990839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, velocity);
991839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
992839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
993839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForOffscreenPosition(float velocity) {
994839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, velocity);
9953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
9963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
997839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private float getDuration(boolean invert, float velocity) {
9983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mView != null) {
9993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
10003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
1001839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
1002839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        Math.pow(viewLp.verticalOffset, 2));
1003dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
1004dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                        Math.pow(0.4f * mSlideAmount, 2));
1005839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1006839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                if (velocity == 0) {
1007839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
1008839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                } else {
1009839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float duration = invert ? d / Math.abs(velocity) :
1010839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            (maxd - d) / Math.abs(velocity);
1011839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (duration < MINIMUM_ANIMATION_DURATION ||
1012839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            duration > DEFAULT_ANIMATION_DURATION) {
1013839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return getDuration(invert, 0);
1014839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    } else {
1015839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return duration;
1016839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    }
1017839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                }
10183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            }
10193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return 0;
10203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
10213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
1022e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
1023e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
1024839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getYProgress() {
102532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mYProgress;
102632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
102732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1028e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
1029e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
1030839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getXProgress() {
103132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mXProgress;
103232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
103344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
103444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    LayoutParams createOrReuseLayoutParams(View v) {
10369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
10379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (currentLp instanceof LayoutParams) {
10389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) currentLp;
10399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setHorizontalOffset(0);
10409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setVerticalOffset(0);
1041839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
1042839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
10439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return lp;
10449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        return new LayoutParams(v);
104632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
104732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
10489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    @Override
10499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1050ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen        checkForAndHandleDataChanged();
10519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final int childCount = getChildCount();
10539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        for (int i = 0; i < childCount; i++) {
10549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            final View child = getChildAt(i);
10559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childRight = mPaddingLeft + child.getMeasuredWidth();
10579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childBottom = mPaddingTop + child.getMeasuredHeight();
10589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
10599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
10619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
10629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        onLayout();
10659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
10669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
106726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    @Override
106826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    public void advance() {
106926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime;
1070c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen
1071c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        if (mAdapter == null) return;
1072ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen        final int adapterCount = getCount();
1073c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen        if (adapterCount == 1 && mLoopViews) return;
1074c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen
107526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        if (mSwipeGestureType == GESTURE_NONE &&
107626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen                timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) {
107726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen            showNext();
107826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        }
107926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    }
108026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
1081839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private void measureChildren() {
1082839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int count = getChildCount();
108336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
108436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int measuredWidth = getMeasuredWidth();
108536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int measuredHeight = getMeasuredHeight();
108636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
108736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int childWidth = Math.round(measuredWidth*(1-PERSPECTIVE_SHIFT_FACTOR_X))
1088026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                - mPaddingLeft - mPaddingRight;
108936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int childHeight = Math.round(measuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR_Y))
1090839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                - mPaddingTop - mPaddingBottom;
1091839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
109236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        int maxWidth = 0;
109336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        int maxHeight = 0;
109436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
1095839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        for (int i = 0; i < count; i++) {
1096839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            final View child = getChildAt(i);
109736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST),
109836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
109936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
110036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            if (child != mHighlight && child != mClickFeedback) {
110136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                final int childMeasuredWidth = child.getMeasuredWidth();
110236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                final int childMeasuredHeight = child.getMeasuredHeight();
110336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                if (childMeasuredWidth > maxWidth) {
110436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    maxWidth = childMeasuredWidth;
110536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                }
110636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                if (childMeasuredHeight > maxHeight) {
110736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    maxHeight = childMeasuredHeight;
110836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                }
110936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            }
111036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
111136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
111236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        mNewPerspectiveShiftX = PERSPECTIVE_SHIFT_FACTOR_X * measuredWidth;
111336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        mNewPerspectiveShiftY = PERSPECTIVE_SHIFT_FACTOR_Y * measuredHeight;
111453838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen
111553838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen        // If we have extra space, we try and spread the items out
111678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (maxWidth > 0 && count > 0 && maxWidth < childWidth) {
111736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mNewPerspectiveShiftX = measuredWidth - maxWidth;
111836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
111936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
112078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen        if (maxHeight > 0 && count > 0 && maxHeight < childHeight) {
112136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mNewPerspectiveShiftY = measuredHeight - maxHeight;
1122839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1123839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
1124839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1125839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    @Override
1126839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1127839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
1128839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
1129839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
1130839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
1131839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1132839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
1133839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1134839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // We need to deal with the case where our parent hasn't told us how
1135839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // big we should be. In this case we should
1136026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorY = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_Y);
1137839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
1138839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ?
1139026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildHeight * (1 + factorY)) +
1140839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingTop + mPaddingBottom : 0;
1141839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
1142189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
1143026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                int height = Math.round(mReferenceChildHeight * (1 + factorY))
1144189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                        + mPaddingTop + mPaddingBottom;
1145189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (height <= heightSpecSize) {
1146189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize = height;
1147189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
1148189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize |= MEASURED_STATE_TOO_SMALL;
1149ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen
1150189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
1151189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
1152189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                heightSpecSize = 0;
1153189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
1154839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1155839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1156026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorX = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_X);
1157839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
1158026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            widthSpecSize = haveChildRefSize ?
1159026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildWidth * (1 + factorX)) +
1160026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    mPaddingLeft + mPaddingRight : 0;
1161839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
1162189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
1163189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
1164189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (width <= widthSpecSize) {
1165189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize = width;
1166189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
1167189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize |= MEASURED_STATE_TOO_SMALL;
1168189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
1169189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
1170189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                widthSpecSize = 0;
1171189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
1172839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1173839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        setMeasuredDimension(widthSpecSize, heightSpecSize);
1174839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        measureChildren();
1175839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
1176839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
11779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    class LayoutParams extends ViewGroup.LayoutParams {
11789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int horizontalOffset;
11799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int verticalOffset;
11809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        View mView;
1181d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect parentRect = new Rect();
1182d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect invalidateRect = new Rect();
1183d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final RectF invalidateRectf = new RectF();
1184d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect globalInvalidateRect = new Rect();
11859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(View view) {
11879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(0, 0);
1188839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1189839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
11909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
11919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
11929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mView = view;
11939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
11949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(Context c, AttributeSet attrs) {
11969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(c, attrs);
11979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
11989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
1199839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1200839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
1201b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
1202b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
12039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void invalidateGlobalRegion(View v, Rect r) {
1204d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            // We need to make a new rect here, so as not to modify the one passed
1205d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            globalInvalidateRect.set(r);
12069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View p = v;
12079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
12089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            boolean firstPass = true;
12109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            parentRect.set(0, 0, 0, 0);
12119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int depth = 0;
1212b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen            while (p.getParent() != null && p.getParent() instanceof View
1213d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    && !parentRect.contains(globalInvalidateRect)) {
12149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (!firstPass) {
1215d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
1216d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                            - p.getScrollY());
12179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    depth++;
12189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                }
12199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                firstPass = false;
12209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                p = (View) p.getParent();
1221b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                parentRect.set(p.getScrollX(), p.getScrollY(),
1222b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                               p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
1223839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
12249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
12259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1226d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top,
1227d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.right, globalInvalidateRect.bottom);
1228d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
1229d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
1230d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        Rect getInvalidateRect() {
1231d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            return invalidateRect;
1232d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
12339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1234d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        void resetInvalidateRect() {
12350ac116b688380489c3690f6f65b282990c221f17Adam Cohen            invalidateRect.set(0, 0, 0, 0);
12369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
123732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1238a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase        // This is public so that ObjectAnimator can access it
12399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setVerticalOffset(int newVerticalOffset) {
12400ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(horizontalOffset, newVerticalOffset);
12419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
12429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setHorizontalOffset(int newHorizontalOffset) {
12440ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(newHorizontalOffset, verticalOffset);
12450ac116b688380489c3690f6f65b282990c221f17Adam Cohen        }
12460ac116b688380489c3690f6f65b282990c221f17Adam Cohen
12470ac116b688380489c3690f6f65b282990c221f17Adam Cohen        public void setOffsets(int newHorizontalOffset, int newVerticalOffset) {
12480ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset;
12499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = newHorizontalOffset;
12500ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int verticalOffsetDelta = newVerticalOffset - verticalOffset;
12510ac116b688380489c3690f6f65b282990c221f17Adam Cohen            verticalOffset = newVerticalOffset;
12529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
12549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
12550ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft());
12560ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight());
12570ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop());
12580ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom());
12590ac116b688380489c3690f6f65b282990c221f17Adam Cohen
12600ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRectf.set(left, top, right, bottom);
12619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
12639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
12649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
12659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
12669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
12679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12680ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
12699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
12709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
12719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
12729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
12749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
12759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
127632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
127732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
12789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static class HolographicHelper {
12799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mHolographicPaint = new Paint();
12809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mErasePaint = new Paint();
1281839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private final Paint mBlurPaint = new Paint();
12828baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int RES_OUT = 0;
12838baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int CLICK_FEEDBACK = 1;
12848baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private float mDensity;
1285e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mSmallBlurMaskFilter;
1286e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mLargeBlurMaskFilter;
1287e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mCanvas = new Canvas();
1288e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mMaskCanvas = new Canvas();
1289e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final int[] mTmpXY = new int[2];
1290e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Matrix mIdentityMatrix = new Matrix();
129132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1292dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        HolographicHelper(Context context) {
12938baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mDensity = context.getResources().getDisplayMetrics().density;
1294dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
12959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setFilterBitmap(true);
1296839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
12979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
12989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setFilterBitmap(true);
1299e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
1300e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL);
1301e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL);
13029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
13039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        Bitmap createOutline(View v) {
13058baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            return createOutline(v, RES_OUT);
13068baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
13078baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
13088baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        Bitmap createOutline(View v, int type) {
13098baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (type == RES_OUT) {
13108baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mHolographicPaint.setColor(0xff6699ff);
1311e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mSmallBlurMaskFilter);
13128baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            } else if (type == CLICK_FEEDBACK) {
13138baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mHolographicPaint.setColor(0x886699ff);
1314e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mLargeBlurMaskFilter);
13158baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
13168baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
13179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
13189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                return null;
13199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
13209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
13229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    Bitmap.Config.ARGB_8888);
1323e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mCanvas.setBitmap(bitmap);
132432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
13259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float rotationX = v.getRotationX();
1326839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float rotation = v.getRotation();
1327839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float translationY = v.getTranslationY();
1328026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            float translationX = v.getTranslationX();
13299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(0);
1330839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(0);
1331839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(0);
1332026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(0);
1333e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            v.draw(mCanvas);
13349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(rotationX);
1335839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(rotation);
1336839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(translationY);
1337026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(translationX);
13389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1339e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            drawOutline(mCanvas, bitmap);
13409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return bitmap;
13419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
13429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
13439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void drawOutline(Canvas dest, Bitmap src) {
1344e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            final int[] xy = mTmpXY;
1345839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
1346e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.setBitmap(mask);
1347e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
13489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawColor(0, PorterDuff.Mode.CLEAR);
1349e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            dest.setMatrix(mIdentityMatrix);
1350839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
13519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mask.recycle();
13529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
135332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
1354ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen}
1355