StackView.java revision 026e121bc35b9012943d8ea86518b51270d0c0ef
144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/*
244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Copyright (C) 2010 The Android Open Source Project
344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Licensed under the Apache License, Version 2.0 (the "License");
544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * you may not use this file except in compliance with the License.
644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * You may obtain a copy of the License at
744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *      http://www.apache.org/licenses/LICENSE-2.0
944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
1044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Unless required by applicable law or agreed to in writing, software
1144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * distributed under the License is distributed on an "AS IS" BASIS,
1244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * See the License for the specific language governing permissions and
1444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * limitations under the License.
1544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
1644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpackage android.widget;
1844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
19a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator;
20e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guyimport android.animation.PropertyValuesHolder;
2144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context;
2232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap;
23839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter;
2432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas;
25026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohenimport android.graphics.Color;
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;
5844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
60839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Parameters effecting the perspective visuals
61839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
62026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_Y = 0.1f;
63026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_X = 0.1f;
64026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
65e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    @SuppressWarnings({"FieldCanBeLocal"})
66026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SCALE_FACTOR = 0.f;
67839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
68839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
69839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Represent the two possible stack modes, one where items slide up, and the other
70839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * where items slide down. The perspective is also inverted between these two modes.
71839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
72839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_UP = 0;
73839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_DOWN = 1;
74839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
75839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
7644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These specify the different gesture states
7744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
785b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_NONE = 0;
795b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_UP = 1;
805b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_DOWN = 2;
8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Specifies how far you need to swipe (up or down) before it
8444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * will be consider a completed gesture when you lift your finger
8544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
86a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    private static final float SWIPE_THRESHOLD_RATIO = 0.2f;
87a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen
88a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    /**
89a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * Specifies the total distance, relative to the size of the stack,
90a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * that views will be slid, either up or down
91a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     */
925b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SLIDE_UP_RATIO = 0.7f;
9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
9444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Sentinel value for no current active pointer.
9644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Used by {@link #mActivePointerId}.
9744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
9844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private static final int INVALID_POINTER = -1;
9944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
101839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
102839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
103839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int NUM_ACTIVE_VIEWS = 5;
104839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
105dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private static final int FRAME_PADDING = 4;
106839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
107e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy    private final Rect mTouchRect = new Rect();
108e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
10926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000;
11026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
111839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These variables are all related to the current state of touch interaction
11344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * with the stack
11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
11544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialY;
11644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialX;
11744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mActivePointerId;
11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mYVelocity = 0;
11944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeGestureType = GESTURE_NONE;
120dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mSlideAmount;
12144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeThreshold;
12244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mTouchSlop;
12344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mMaximumVelocity;
12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private VelocityTracker mVelocityTracker;
1253352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private boolean mTransitionIsSetup = false;
12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static HolographicHelper sHolographicHelper;
12832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private ImageView mHighlight;
1298baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private ImageView mClickFeedback;
1308baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private boolean mClickFeedbackIsValid = false;
13132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private StackSlider mStackSlider;
13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private boolean mFirstLayoutHappened = false;
13326e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private long mLastInteractionTime = 0;
134839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private int mStackMode;
135dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mFramePadding;
1360ac116b688380489c3690f6f65b282990c221f17Adam Cohen    private final Rect stackInvalidateRect = new Rect();
13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context) {
13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context);
14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
14144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
14244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context, AttributeSet attrs) {
14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context, attrs);
14544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void initStackView() {
14996d8d56302da81b24333b204e6d7f15064538036Adam Cohen        configureViewAnimator(NUM_ACTIVE_VIEWS, 1);
15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        setStaticTransformationsEnabled(true);
15144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
152b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        mTouchSlop = configuration.getScaledTouchSlop();
15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
15532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
15632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight = new ImageView(getContext());
15732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
15832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
1598baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
1608baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback = new ImageView(getContext());
1618baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback));
1628baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback));
1638baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
1648baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
16532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mStackSlider = new StackSlider();
16632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (sHolographicHelper == null) {
168dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            sHolographicHelper = new HolographicHelper(mContext);
16932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
1709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipChildren(false);
1719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipToPadding(false);
1721480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen
173839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // This sets the form of the StackView, which is currently to have the perspective-shifted
174839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // views above the active view, and have items slide down when sliding out. The opposite is
175839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // available by using ITEMS_SLIDE_UP.
176839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        mStackMode = ITEMS_SLIDE_DOWN;
177839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1781480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // This is a flag to indicate the the stack is loading for the first time
1791480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        mWhichChild = -1;
180dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
181dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // Adjust the frame padding based on the density, since the highlight changes based
182dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // on the density
183dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        final float density = mContext.getResources().getDisplayMetrics().density;
184dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
18544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
18644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
18744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
18844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
18944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
19044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void animateViewForTransition(int fromIndex, int toIndex, View view) {
191026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        if (fromIndex == -1 && toIndex > 0) {
19244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item in
19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (view.getAlpha() == 1) {
19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                view.setAlpha(0);
19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
196b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(VISIBLE);
197b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
1982794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f);
199026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            fadeIn.setDuration(FADE_IN_ANIMATION_DURATION);
20044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            fadeIn.start();
20196d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 0 && toIndex == 1) {
20244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item in
20344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setVisibility(VISIBLE);
20432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
205839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
20644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
207b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
2082794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f);
2092794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
2102794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
211839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    slideInX, slideInY);
2122794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
213839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
214839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
21596d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 1 && toIndex == 0) {
21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item out
217839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
21844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
219b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
2202794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f);
2212794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
2222794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
2232794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    slideOutX, slideOutY);
2242794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
225839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
226839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
22796d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == -1 && toIndex == 0) {
22844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Make sure this view that is "waiting in the wings" is invisible
22944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setAlpha(0.0f);
230b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(INVISIBLE);
231b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
232dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            lp.setVerticalOffset(-mSlideAmount);
23344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (toIndex == -1) {
23444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item out
2352794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f);
2362794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            fadeOut.setDuration(DEFAULT_ANIMATION_DURATION);
23744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            fadeOut.start();
23844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
239839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
240839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // Implement the faked perspective
241839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (toIndex != -1) {
242f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            transformViewAtIndex(toIndex, view);
243f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        }
244f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    }
245839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
246026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private void transformViewAtIndex(int index, View view) {
247026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float maxPerspectiveShiftY = getMeasuredHeight() * PERSPECTIVE_SHIFT_FACTOR_Y;
248026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float maxPerspectiveShiftX = getMeasuredHeight() * PERSPECTIVE_SHIFT_FACTOR_X;
249026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
250026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        index = mMaxNumActiveViews - index - 1;
251026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        if (index == mMaxNumActiveViews - 1) index--;
252026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
253026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float r = (index * 1.0f) / (mMaxNumActiveViews - 2);
254026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
255026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
256026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        PropertyValuesHolder scalePropX = PropertyValuesHolder.ofFloat("scaleX", scale);
257026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        PropertyValuesHolder scalePropY = PropertyValuesHolder.ofFloat("scaleY", scale);
258026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
259026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
260026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float perspectiveTranslationY = -stackDirection * r * maxPerspectiveShiftY;
261026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float scaleShiftCorrectionY = stackDirection * (1 - scale) *
262026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR_Y) / 2.0f);
263026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float transY = perspectiveTranslationY + scaleShiftCorrectionY;
264026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
265026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float perspectiveTranslationX = (1 - r) * maxPerspectiveShiftX;
266026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float scaleShiftCorrectionX =  (1 - scale) *
267026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredWidth() * (1 - PERSPECTIVE_SHIFT_FACTOR_X) / 2.0f);
268026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float transX = perspectiveTranslationX + scaleShiftCorrectionX;
269026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
270026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", transX);
271026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY);
272026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
273026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(view, scalePropX, scalePropY,
274026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                translationY, translationX);
275026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        pa.setDuration(100);
276026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        pa.start();
277026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    }
278026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
2793352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private void setupStackSlider(View v, int mode) {
2803352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mStackSlider.setMode(mode);
2813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (v != null) {
2823352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
2833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setRotation(v.getRotation());
2843352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setTranslationY(v.getTranslationY());
285026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            mHighlight.setTranslationX(v.getTranslationX());
2863352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.bringToFront();
2873352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.bringToFront();
2883352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mStackSlider.setView(v);
2893352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
2903352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.setVisibility(VISIBLE);
2913352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
2923352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
2933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
2943352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
2953352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
2963352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showNext() {
2970ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
2983352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
2993352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(1);
3003352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
3013352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
3023352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(0);
3033352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
3043352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
3053352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3063352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showNext();
3073352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3083352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3093352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
3103352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
3113352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showPrevious() {
3120ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
3133352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
3143352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(0);
3153352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
3163352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
3173352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(1);
3183352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
3193352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
3203352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3213352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showPrevious();
3223352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3233352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
32496d8d56302da81b24333b204e6d7f15064538036Adam Cohen    @Override
32596d8d56302da81b24333b204e6d7f15064538036Adam Cohen    void showOnly(int childIndex, boolean animate, boolean onLayout) {
32696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        super.showOnly(childIndex, animate, onLayout);
32796d8d56302da81b24333b204e6d7f15064538036Adam Cohen
32896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        // Here we need to make sure that the z-order of the children is correct
3293352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
33096d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int index = modulo(i, getWindowSize());
3310ac116b688380489c3690f6f65b282990c221f17Adam Cohen            ViewAndIndex vi = mViewsMap.get(index);
3320ac116b688380489c3690f6f65b282990c221f17Adam Cohen            if (vi != null) {
3330ac116b688380489c3690f6f65b282990c221f17Adam Cohen                View v = mViewsMap.get(index).view;
3340ac116b688380489c3690f6f65b282990c221f17Adam Cohen                if (v != null) v.bringToFront();
3350ac116b688380489c3690f6f65b282990c221f17Adam Cohen            }
33696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        }
3373352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mTransitionIsSetup = false;
3388baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedbackIsValid = false;
3398baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
3408baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
3418baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void updateClickFeedback() {
3428baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        if (!mClickFeedbackIsValid) {
3439c295482dd739e80dd49ea0dd3102ad6be6742ddAdam Cohen            View v = getViewAtRelativeIndex(1);
3448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (v != null) {
3458baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v,
3468baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                        HolographicHelper.CLICK_FEEDBACK));
3478baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationX(v.getTranslationX());
3488baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationY(v.getTranslationY());
3498baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
3508baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mClickFeedbackIsValid = true;
3518baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
3528baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
3538baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
3548baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
3558baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void showTapFeedback(View v) {
3568baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        updateClickFeedback();
3578baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(VISIBLE);
3588baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.bringToFront();
3598baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
3608baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
3618baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
3628baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
3638baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void hideTapFeedback(View v) {
3648baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
3658baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
36696d8d56302da81b24333b204e6d7f15064538036Adam Cohen    }
36796d8d56302da81b24333b204e6d7f15064538036Adam Cohen
368f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void updateChildTransforms() {
36996d8d56302da81b24333b204e6d7f15064538036Adam Cohen        for (int i = 0; i < getNumActiveViews(); i++) {
370f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            View v = getViewAtRelativeIndex(i);
371f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            if (v != null) {
372f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen                transformViewAtIndex(i, v);
373f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            }
374839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
37544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
37644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
377dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    @Override
378dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    FrameLayout getFrameForChild() {
379dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        FrameLayout fl = new FrameLayout(mContext);
380dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
381dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        return fl;
382dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    }
383dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
38444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
38544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Apply any necessary tranforms for the child that is being added.
38644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
38744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void applyTransformForChildAtIndex(View child, int relativeIndex) {
38844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
38944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
39044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
3919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void dispatchDraw(Canvas canvas) {
3920ac116b688380489c3690f6f65b282990c221f17Adam Cohen        canvas.getClipBounds(stackInvalidateRect);
393d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        final int childCount = getChildCount();
394d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        for (int i = 0; i < childCount; i++) {
395d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
3960ac116b688380489c3690f6f65b282990c221f17Adam Cohen            stackInvalidateRect.union(lp.getInvalidateRect());
397d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            lp.resetInvalidateRect();
39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
399d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.save(Canvas.CLIP_SAVE_FLAG);
4000ac116b688380489c3690f6f65b282990c221f17Adam Cohen        canvas.clipRect(stackInvalidateRect, Region.Op.UNION);
401d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        super.dispatchDraw(canvas);
402d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.restore();
4039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void onLayout() {
40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mFirstLayoutHappened) {
407dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
408f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            updateChildTransforms();
409dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount);
41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mFirstLayoutHappened = true;
41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
41344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
41444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
41544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onInterceptTouchEvent(MotionEvent ev) {
41644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
41744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch(action & MotionEvent.ACTION_MASK) {
41844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_DOWN: {
41944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (mActivePointerId == INVALID_POINTER) {
42044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialX = ev.getX();
42144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialY = ev.getY();
42244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mActivePointerId = ev.getPointerId(0);
42344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
42444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
42544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
42644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
42744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                int pointerIndex = ev.findPointerIndex(mActivePointerId);
42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (pointerIndex == INVALID_POINTER) {
42944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    // no data for our primary pointer, this shouldn't happen, log it
43044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    Log.d(TAG, "Error: No data for our primary pointer.");
43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    return false;
43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
43344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float newY = ev.getY(pointerIndex);
43444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float deltaY = newY - mInitialY;
43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
43632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
43744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
43844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
43944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
44044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP:
44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
44744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
44844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
44944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
45044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return mSwipeGestureType != GESTURE_NONE;
45144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
45244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
45332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private void beginGestureIfNeeded(float deltaY) {
45432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
4553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
45632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            cancelLongPress();
45732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            requestDisallowInterceptTouchEvent(true);
45832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
459d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            if (mAdapter == null) return;
460d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
461839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int activeIndex;
462839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
46396d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
464839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
46596d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
466839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
46732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
4683352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            int stackMode;
4691b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen            if (mLoopViews) {
4703352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
47196d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == -1) {
47296d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex++;
4733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.BEGINNING_OF_STACK_MODE;
47496d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount() - 1) {
4753352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.END_OF_STACK_MODE;
4763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else {
4773352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
47832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
4793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
4803352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE;
4813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
4823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            View v = getViewAtRelativeIndex(activeIndex);
4833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (v == null) return;
4843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
4853352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            setupStackSlider(v, stackMode);
4863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
4873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // We only register this gesture if we've made it this far without a problem
4883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mSwipeGestureType = swipeGestureType;
489a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen            cancelHandleClick();
49032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
49132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
49232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
49344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
49444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onTouchEvent(MotionEvent ev) {
495a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen        super.onTouchEvent(ev);
496a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen
49744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
49844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
49944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerIndex == INVALID_POINTER) {
50044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // no data for our primary pointer, this shouldn't happen, log it
50144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            Log.d(TAG, "Error: No data for our primary pointer.");
50244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            return false;
50344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
50444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
50544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
50632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float newX = ev.getX(pointerIndex);
50744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float deltaY = newY - mInitialY;
50832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float deltaX = newX - mInitialX;
50944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker == null) {
51044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = VelocityTracker.obtain();
51144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
51244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mVelocityTracker.addMovement(ev);
51344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
51444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch (action & MotionEvent.ACTION_MASK) {
51544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
51632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
51732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
518dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float rx = deltaX / (mSlideAmount * 1.0f);
51932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
520dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
521839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
52232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(1 - r);
52332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
52432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
52532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
526dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
527839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
52832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(r);
52932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
53032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
53144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
53244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
53344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
53444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP: {
53544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                handlePointerUp(ev);
53644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
53744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
53844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
53944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
54044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
54144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
54244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
54344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
54444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
54544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
54644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
54744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
54844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return true;
54944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
55044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
55144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void onSecondaryPointerUp(MotionEvent ev) {
55244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int activePointerIndex = ev.getActionIndex();
55344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int pointerId = ev.getPointerId(activePointerIndex);
55444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerId == mActivePointerId) {
55544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
55696d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
55744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
55844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            View v = getViewAtRelativeIndex(activeViewIndex);
55944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (v == null) return;
56044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
56144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Our primary pointer has gone up -- let's see if we can find
56244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // another pointer on the view. If so, then we should replace
56344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // our primary pointer with this new pointer and adjust things
56444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // so that the view doesn't jump
56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            for (int index = 0; index < ev.getPointerCount(); index++) {
56644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (index != activePointerIndex) {
56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
56844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float x = ev.getX(index);
56944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float y = ev.getY(index);
57044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
571e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
572e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    if (mTouchRect.contains(Math.round(x), Math.round(y))) {
57344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldX = ev.getX(activePointerIndex);
57444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldY = ev.getY(activePointerIndex);
57544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
57644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // adjust our frame of reference to avoid a jump
57744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialY += (y - oldY);
57844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialX += (x - oldX);
57944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
58044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mActivePointerId = ev.getPointerId(index);
58144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        if (mVelocityTracker != null) {
58244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                            mVelocityTracker.clear();
58344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        }
58444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // ok, we're good, we found a new pointer which is touching the active view
58544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        return;
58644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    }
58744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
58844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
58944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
5903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // so end the gesture
59144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            handlePointerUp(ev);
59244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
59344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
59444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
59544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void handlePointerUp(MotionEvent ev) {
59644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
59744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
59844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int deltaY = (int) (newY - mInitialY);
59926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        mLastInteractionTime = System.currentTimeMillis();
60044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (mVelocityTracker != null) {
6023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
6033d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
6043d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
60544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
60644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker != null) {
60744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker.recycle();
60844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = null;
60944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
61044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
6123d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
6130ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
6140ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
6150ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
6160ac116b688380489c3690f6f65b282990c221f17Adam Cohen
61744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe down
618839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
619839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showPrevious();
62096d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
62196d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showNext();
622839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
62332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
6243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
6253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
6260ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
6270ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
6280ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
6290ac116b688380489c3690f6f65b282990c221f17Adam Cohen
63044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe up
631839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
632839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showNext();
63396d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
63496d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showPrevious();
635839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
636839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
63732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
638839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
63944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe up far enough, snap back down
640839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
641839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
642839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
643839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
644839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
645839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
646839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
64732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
648b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
6492794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress);
6502794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
6512794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
652839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
6532794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
654839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
655839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
65632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
65744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe down far enough, snap back up
658839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
659839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
660839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
661839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
662839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
663839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
664839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
6653d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
666b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
6672794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY =
6682794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    PropertyValuesHolder.ofFloat("YProgress",finalYProgress);
6692794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
6702794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
671839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
6722794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
673839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
67444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
67544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
67644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
67744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mSwipeGestureType = GESTURE_NONE;
67832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
67932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
68032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private class StackSlider {
68132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        View mView;
68232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mYProgress;
68332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mXProgress;
68432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int NORMAL_MODE = 0;
6863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int BEGINNING_OF_STACK_MODE = 1;
6873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int END_OF_STACK_MODE = 2;
6883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        int mMode = NORMAL_MODE;
6903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
691b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider() {
692b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
693b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
694b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider(StackSlider copy) {
695b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mView = copy.mView;
696b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mYProgress = copy.mYProgress;
697b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mXProgress = copy.mXProgress;
6983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = copy.mMode;
699b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
700b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
70132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float cubic(float r) {
702839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
70332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
70432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
70532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float highlightAlphaInterpolator(float r) {
70632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.4f;
70732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r < pivot) {
708839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(r / pivot);
70932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
710839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
71132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
71232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
71332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
71432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float viewAlphaInterpolator(float r) {
71532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.3f;
71632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r > pivot) {
717839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
71832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
71932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0;
72032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
72132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
72232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
723b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        private float rotationInterpolator(float r) {
724b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            float pivot = 0.2f;
725b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            if (r < pivot) {
726b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return 0;
727b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            } else {
728839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
729b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
730b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
731b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
73232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        void setView(View v) {
73332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mView = v;
73432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
73532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
73632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setYProgress(float r) {
73732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
73832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.min(1.0f, r);
73932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.max(0, r);
74032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
74132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mYProgress = r;
742a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
743a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen
74432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
74532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
74632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
747839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
748839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
7493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            switch (mMode) {
7503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case NORMAL_MODE:
751dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
752dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
7533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
7543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
755839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float alpha = viewAlphaInterpolator(1 - r);
7563d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
7573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
7583d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // so that they don't interfere with click events.
7593d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
7603d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(VISIBLE);
7613d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    } else if (alpha == 0 && mView.getAlpha() != 0
7623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                            && mView.getVisibility() == VISIBLE) {
7633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(INVISIBLE);
7643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    }
765b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
7663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setAlpha(alpha);
767839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
768839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
7693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
77096d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case END_OF_STACK_MODE:
771839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = r * 0.2f;
772dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
773dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
7743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
7753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
77696d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case BEGINNING_OF_STACK_MODE:
777839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = (1-r) * 0.2f;
778dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
779dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
7803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
7813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
782b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
78332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
78432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
78532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setXProgress(float r) {
78632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
7873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.min(2.0f, r);
7883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.max(-2.0f, r);
78932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
79032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mXProgress = r;
79132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
792a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
79332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
79432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
79532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r *= 0.2f;
797dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
798dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
79932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
80032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        void setMode(int mode) {
8023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = mode;
8033d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
8043d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
8053d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForNeutralPosition() {
806839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, 0);
8073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
8083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
8093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForOffscreenPosition() {
810839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, 0);
811839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
812839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
813839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForNeutralPosition(float velocity) {
814839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, velocity);
815839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
816839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
817839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForOffscreenPosition(float velocity) {
818839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, velocity);
8193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
8203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
821839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private float getDuration(boolean invert, float velocity) {
8223d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mView != null) {
8233d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
8243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
825839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
826839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        Math.pow(viewLp.verticalOffset, 2));
827dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
828dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                        Math.pow(0.4f * mSlideAmount, 2));
829839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
830839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                if (velocity == 0) {
831839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
832839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                } else {
833839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float duration = invert ? d / Math.abs(velocity) :
834839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            (maxd - d) / Math.abs(velocity);
835839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (duration < MINIMUM_ANIMATION_DURATION ||
836839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            duration > DEFAULT_ANIMATION_DURATION) {
837839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return getDuration(invert, 0);
838839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    } else {
839839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return duration;
840839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    }
841839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                }
8423d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            }
8433d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return 0;
8443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
8453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
846e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
847e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
848839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getYProgress() {
84932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mYProgress;
85032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
85132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
852e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
853e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
854839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getXProgress() {
85532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mXProgress;
85632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
85744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
85844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
85944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
86044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public void onRemoteAdapterConnected() {
86144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super.onRemoteAdapterConnected();
8621480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // On first run, we want to set the stack to the end.
86396d8d56302da81b24333b204e6d7f15064538036Adam Cohen        if (mWhichChild == -1) {
86496d8d56302da81b24333b204e6d7f15064538036Adam Cohen            mWhichChild = 0;
8653042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen        }
86696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        setDisplayedChild(mWhichChild);
86744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
86832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    LayoutParams createOrReuseLayoutParams(View v) {
8709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
8719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (currentLp instanceof LayoutParams) {
8729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) currentLp;
8739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setHorizontalOffset(0);
8749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setVerticalOffset(0);
875839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
876839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
8779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return lp;
8789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        return new LayoutParams(v);
88032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
88132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    @Override
8839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        boolean dataChanged = mDataChanged;
8859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (dataChanged) {
8869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            handleDataChanged();
8879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // if the data changes, mWhichChild might be out of the bounds of the adapter
8899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // in this case, we reset mWhichChild to the beginning
8909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mWhichChild >= mAdapter.getCount())
8919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mWhichChild = 0;
8929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            showOnly(mWhichChild, true, true);
8946364f2bbe5254b4274f3feffc48f4259eacc205eWinson Chung            refreshChildren();
8959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final int childCount = getChildCount();
8989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        for (int i = 0; i < childCount; i++) {
8999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            final View child = getChildAt(i);
9009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childRight = mPaddingLeft + child.getMeasuredWidth();
9029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childBottom = mPaddingTop + child.getMeasuredHeight();
9039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
9049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
9069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
9079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        mDataChanged = false;
9119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        onLayout();
9129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
9139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
91426e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    @Override
91526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    public void advance() {
91626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime;
91726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        if (mSwipeGestureType == GESTURE_NONE &&
91826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen                timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) {
91926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen            showNext();
92026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        }
92126e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    }
92226e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
923839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private void measureChildren() {
924839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int count = getChildCount();
925026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        final int childWidth = Math.round(getMeasuredWidth()*(1-PERSPECTIVE_SHIFT_FACTOR_X))
926026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                - mPaddingLeft - mPaddingRight;
927026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        final int childHeight = Math.round(getMeasuredHeight()*(1-PERSPECTIVE_SHIFT_FACTOR_Y))
928839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                - mPaddingTop - mPaddingBottom;
929839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
930839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        for (int i = 0; i < count; i++) {
931839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            final View child = getChildAt(i);
932839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
933839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
934839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
935839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
936839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
937839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    @Override
938839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
939839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
940839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
941839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
942839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
943839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
944839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
945839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
946839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // We need to deal with the case where our parent hasn't told us how
947839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // big we should be. In this case we should
948026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorY = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_Y);
949839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
950839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ?
951026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildHeight * (1 + factorY)) +
952839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingTop + mPaddingBottom : 0;
953839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
954189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
955026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                int height = Math.round(mReferenceChildHeight * (1 + factorY))
956189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                        + mPaddingTop + mPaddingBottom;
957189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (height <= heightSpecSize) {
958189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize = height;
959189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
960189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize |= MEASURED_STATE_TOO_SMALL;
961189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
962189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
963189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                heightSpecSize = 0;
964189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
965839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
966839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
967026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorX = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_X);
968839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
969026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            widthSpecSize = haveChildRefSize ?
970026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildWidth * (1 + factorX)) +
971026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    mPaddingLeft + mPaddingRight : 0;
972839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
973189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
974189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
975189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (width <= widthSpecSize) {
976189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize = width;
977189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
978189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize |= MEASURED_STATE_TOO_SMALL;
979189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
980189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
981189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                widthSpecSize = 0;
982189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
983839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
984839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
985839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        setMeasuredDimension(widthSpecSize, heightSpecSize);
986839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        measureChildren();
987839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
988839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
9899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    class LayoutParams extends ViewGroup.LayoutParams {
9909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int horizontalOffset;
9919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int verticalOffset;
9929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        View mView;
993d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect parentRect = new Rect();
994d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect invalidateRect = new Rect();
995d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final RectF invalidateRectf = new RectF();
996d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect globalInvalidateRect = new Rect();
9979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(View view) {
9999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(0, 0);
1000839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1001839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
10029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
10039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
10049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mView = view;
10059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(Context c, AttributeSet attrs) {
10089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(c, attrs);
10099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
10109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
1011839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1012839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
1013b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
1014b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
10159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void invalidateGlobalRegion(View v, Rect r) {
1016d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            // We need to make a new rect here, so as not to modify the one passed
1017d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            globalInvalidateRect.set(r);
10189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View p = v;
10199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
10209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            boolean firstPass = true;
10229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            parentRect.set(0, 0, 0, 0);
10239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int depth = 0;
1024b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen            while (p.getParent() != null && p.getParent() instanceof View
1025d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    && !parentRect.contains(globalInvalidateRect)) {
10269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (!firstPass) {
1027d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
1028d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                            - p.getScrollY());
10299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    depth++;
10309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                }
10319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                firstPass = false;
10329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                p = (View) p.getParent();
1033b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                parentRect.set(p.getScrollX(), p.getScrollY(),
1034b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                               p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
1035839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
10369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
10379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1038d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top,
1039d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.right, globalInvalidateRect.bottom);
1040d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
1041d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
1042d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        Rect getInvalidateRect() {
1043d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            return invalidateRect;
1044d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
10459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1046d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        void resetInvalidateRect() {
10470ac116b688380489c3690f6f65b282990c221f17Adam Cohen            invalidateRect.set(0, 0, 0, 0);
10489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
104932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1050a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase        // This is public so that ObjectAnimator can access it
10519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setVerticalOffset(int newVerticalOffset) {
10520ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(horizontalOffset, newVerticalOffset);
10539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setHorizontalOffset(int newHorizontalOffset) {
10560ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(newHorizontalOffset, verticalOffset);
10570ac116b688380489c3690f6f65b282990c221f17Adam Cohen        }
10580ac116b688380489c3690f6f65b282990c221f17Adam Cohen
10590ac116b688380489c3690f6f65b282990c221f17Adam Cohen        public void setOffsets(int newHorizontalOffset, int newVerticalOffset) {
10600ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset;
10619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = newHorizontalOffset;
10620ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int verticalOffsetDelta = newVerticalOffset - verticalOffset;
10630ac116b688380489c3690f6f65b282990c221f17Adam Cohen            verticalOffset = newVerticalOffset;
10649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
10669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
10670ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft());
10680ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight());
10690ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop());
10700ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom());
10710ac116b688380489c3690f6f65b282990c221f17Adam Cohen
10720ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRectf.set(left, top, right, bottom);
10739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
10759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
10769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
10779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
10789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
10799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10800ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
10819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
10829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
10839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
10849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
10869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
10879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
108832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
108932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
10909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static class HolographicHelper {
10919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mHolographicPaint = new Paint();
10929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mErasePaint = new Paint();
1093839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private final Paint mBlurPaint = new Paint();
10948baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int RES_OUT = 0;
10958baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int CLICK_FEEDBACK = 1;
10968baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private float mDensity;
1097e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mSmallBlurMaskFilter;
1098e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mLargeBlurMaskFilter;
1099e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mCanvas = new Canvas();
1100e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mMaskCanvas = new Canvas();
1101e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final int[] mTmpXY = new int[2];
1102e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Matrix mIdentityMatrix = new Matrix();
110332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1104dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        HolographicHelper(Context context) {
11058baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mDensity = context.getResources().getDisplayMetrics().density;
1106dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
11079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setFilterBitmap(true);
1108839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
11099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
11109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setFilterBitmap(true);
1111e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
1112e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL);
1113e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL);
11149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
11159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        Bitmap createOutline(View v) {
11178baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            return createOutline(v, RES_OUT);
11188baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
11198baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
11208baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        Bitmap createOutline(View v, int type) {
11218baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (type == RES_OUT) {
11228baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mHolographicPaint.setColor(0xff6699ff);
1123e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mSmallBlurMaskFilter);
11248baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            } else if (type == CLICK_FEEDBACK) {
11258baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mHolographicPaint.setColor(0x886699ff);
1126e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mLargeBlurMaskFilter);
11278baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
11288baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
11299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
11309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                return null;
11319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
11329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
11349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    Bitmap.Config.ARGB_8888);
1135e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mCanvas.setBitmap(bitmap);
113632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
11379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float rotationX = v.getRotationX();
1138839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float rotation = v.getRotation();
1139839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float translationY = v.getTranslationY();
1140026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            float translationX = v.getTranslationX();
11419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(0);
1142839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(0);
1143839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(0);
1144026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(0);
1145e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            v.draw(mCanvas);
11469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(rotationX);
1147839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(rotation);
1148839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(translationY);
1149026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(translationX);
11509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1151e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            drawOutline(mCanvas, bitmap);
11529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return bitmap;
11539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
11549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void drawOutline(Canvas dest, Bitmap src) {
1156e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            final int[] xy = mTmpXY;
1157839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
1158e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.setBitmap(mask);
1159e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
11609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawColor(0, PorterDuff.Mode.CLEAR);
1161e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            dest.setMatrix(mIdentityMatrix);
1162839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
11639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mask.recycle();
11649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
116532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
116644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen}
1167