StackView.java revision 69d66e00136f67332c992326a7b2eb334eeb32a2
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
19c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohenimport java.lang.ref.WeakReference;
20c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen
21a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator;
22e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guyimport android.animation.PropertyValuesHolder;
2344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context;
2432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap;
25839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter;
2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas;
2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix;
2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint;
2932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff;
3032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode;
3144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect;
329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF;
33d51bbb5b56446519db88f49f2682da782b0069acAdam Cohenimport android.graphics.Region;
34839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter;
3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet;
3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log;
3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent;
3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker;
3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View;
4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration;
4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup;
42b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator;
4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView;
4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView
4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/**
4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe
4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children.
4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator {
5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final String TAG = "StackView";
5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Default animation parameters
5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
56e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int DEFAULT_ANIMATION_DURATION = 400;
57026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final int FADE_IN_ANIMATION_DURATION = 800;
58e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int MINIMUM_ANIMATION_DURATION = 50;
59c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen    private static final int STACK_RELAYOUT_DURATION = 100;
6044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
62839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Parameters effecting the perspective visuals
63839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
64026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_Y = 0.1f;
65026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SHIFT_FACTOR_X = 0.1f;
66026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
6736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mPerspectiveShiftX;
6836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mPerspectiveShiftY;
6936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mNewPerspectiveShiftX;
7036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private float mNewPerspectiveShiftY;
7136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
72e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    @SuppressWarnings({"FieldCanBeLocal"})
73026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    private static final float PERSPECTIVE_SCALE_FACTOR = 0.f;
74839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
75839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
76839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Represent the two possible stack modes, one where items slide up, and the other
77839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * where items slide down. The perspective is also inverted between these two modes.
78839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
79839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_UP = 0;
80839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_DOWN = 1;
81839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
82839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These specify the different gesture states
8444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
855b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_NONE = 0;
865b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_UP = 1;
875b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_DOWN = 2;
8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Specifies how far you need to swipe (up or down) before it
9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * will be consider a completed gesture when you lift your finger
9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
93a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    private static final float SWIPE_THRESHOLD_RATIO = 0.2f;
94a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen
95a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    /**
96a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * Specifies the total distance, relative to the size of the stack,
97a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * that views will be slid, either up or down
98a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     */
995b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SLIDE_UP_RATIO = 0.7f;
10044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Sentinel value for no current active pointer.
10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Used by {@link #mActivePointerId}.
10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private static final int INVALID_POINTER = -1;
10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
108839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
109839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
110839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int NUM_ACTIVE_VIEWS = 5;
111839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
112dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private static final int FRAME_PADDING = 4;
113839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
114e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy    private final Rect mTouchRect = new Rect();
115e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
11626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000;
11726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
118839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
11944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These variables are all related to the current state of touch interaction
12044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * with the stack
12144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
12244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialY;
12344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialX;
12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mActivePointerId;
12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mYVelocity = 0;
12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeGestureType = GESTURE_NONE;
127dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mSlideAmount;
12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeThreshold;
12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mTouchSlop;
13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mMaximumVelocity;
13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private VelocityTracker mVelocityTracker;
1323352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private boolean mTransitionIsSetup = false;
13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static HolographicHelper sHolographicHelper;
13532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private ImageView mHighlight;
1368baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private ImageView mClickFeedback;
1378baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    private boolean mClickFeedbackIsValid = false;
13832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private StackSlider mStackSlider;
13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private boolean mFirstLayoutHappened = false;
14026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    private long mLastInteractionTime = 0;
141839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private int mStackMode;
142dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mFramePadding;
1430ac116b688380489c3690f6f65b282990c221f17Adam Cohen    private final Rect stackInvalidateRect = new Rect();
14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
14544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context) {
14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context);
14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
14944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context, AttributeSet attrs) {
15144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context, attrs);
15244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
15544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void initStackView() {
15696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        configureViewAnimator(NUM_ACTIVE_VIEWS, 1);
15744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        setStaticTransformationsEnabled(true);
15844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
159b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        mTouchSlop = configuration.getScaledTouchSlop();
16044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
16144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
16232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
16332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight = new ImageView(getContext());
16432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
16532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
1668baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
1678baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback = new ImageView(getContext());
1688baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback));
1698baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback));
1708baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
1718baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
17232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mStackSlider = new StackSlider();
17332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (sHolographicHelper == null) {
175dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            sHolographicHelper = new HolographicHelper(mContext);
17632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
1779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipChildren(false);
1789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipToPadding(false);
1791480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen
180839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // This sets the form of the StackView, which is currently to have the perspective-shifted
181839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // views above the active view, and have items slide down when sliding out. The opposite is
182839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // available by using ITEMS_SLIDE_UP.
183839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        mStackMode = ITEMS_SLIDE_DOWN;
184839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1851480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // This is a flag to indicate the the stack is loading for the first time
1861480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        mWhichChild = -1;
187dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
188dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // Adjust the frame padding based on the density, since the highlight changes based
189dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // on the density
190dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        final float density = mContext.getResources().getDisplayMetrics().density;
191dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
19244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
19769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen    void animateViewForTransition(int fromIndex, int toIndex, final View view) {
19869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        ObjectAnimator alphaOa = null;
19969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        ObjectAnimator oldAlphaOa = null;
20069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
20169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        // If there is currently an alpha animation on this view, we need
20269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        // to know about it, and may need to cancel it so as not to interfere with
20369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        // a new alpha animation.
20469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        Object tag = view.getTag(com.android.internal.R.id.viewAlphaAnimation);
20569d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        if (tag instanceof WeakReference<?>) {
20669d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            Object obj = ((WeakReference<?>) tag).get();
20769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            if (obj instanceof ObjectAnimator) {
20869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen                oldAlphaOa = (ObjectAnimator) obj;
20969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            }
21069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        }
21169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
212c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen        if (fromIndex == -1 && toIndex == NUM_ACTIVE_VIEWS -1) {
21344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item in
21444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (view.getAlpha() == 1) {
21544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                view.setAlpha(0);
21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
217c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setScaleX(1 - PERSPECTIVE_SCALE_FACTOR);
218c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setScaleY(1 - PERSPECTIVE_SCALE_FACTOR);
219c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setTranslationX(mPerspectiveShiftX);
220c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setTranslationY(0);
221b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(VISIBLE);
222b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
22369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f);
22469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            alphaOa.setDuration(FADE_IN_ANIMATION_DURATION);
22569d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            if (oldAlphaOa != null) oldAlphaOa.cancel();
22669d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            alphaOa.start();
22769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setTagInternal(com.android.internal.R.id.viewAlphaAnimation,
22869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen                    new WeakReference<ObjectAnimator>(alphaOa));
22996d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 0 && toIndex == 1) {
23044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item in
23144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setVisibility(VISIBLE);
23232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
233839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
23444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
235b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
23669d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            animationSlider.setView(view);
2372794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f);
2382794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
23969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            ObjectAnimator slideIn = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
240839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    slideInX, slideInY);
24169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            slideIn.setDuration(duration);
24269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            slideIn.setInterpolator(new LinearInterpolator());
24369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            slideIn.start();
24496d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 1 && toIndex == 0) {
24544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item out
246839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
24744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
248b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
24969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            animationSlider.setView(view);
2502794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f);
2512794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
25269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            ObjectAnimator slideOut = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
2532794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    slideOutX, slideOutY);
25469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            slideOut.setDuration(duration);
25569d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            slideOut.setInterpolator(new LinearInterpolator());
25669d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            slideOut.start();
25769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        } else if (toIndex == 0) {
25844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Make sure this view that is "waiting in the wings" is invisible
25944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setAlpha(0.0f);
260b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(INVISIBLE);
26169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        } else if (fromIndex == 0 && toIndex > 1) {
26269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setVisibility(VISIBLE);
26369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setAlpha(1.0f);
264c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen        } else if (fromIndex == -1) {
265c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setAlpha(1.0f);
266c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setVisibility(VISIBLE);
26744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (toIndex == -1) {
26844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item out
26969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f);
27069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            alphaOa.setDuration(STACK_RELAYOUT_DURATION);
27169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            if (oldAlphaOa != null) oldAlphaOa.cancel();
27269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            alphaOa.start();
27369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            view.setTagInternal(com.android.internal.R.id.viewAlphaAnimation,
27469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen                    new WeakReference<ObjectAnimator>(alphaOa));
27544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
276839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
277839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // Implement the faked perspective
278839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (toIndex != -1) {
27936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            transformViewAtIndex(toIndex, view, true);
280f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        }
281f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    }
282839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
28336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen    private void transformViewAtIndex(int index, final View view, boolean animate) {
28436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float maxPerspectiveShiftY = mPerspectiveShiftY;
28536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float maxPerspectiveShiftX = mPerspectiveShiftX;
286026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
287026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        index = mMaxNumActiveViews - index - 1;
288026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        if (index == mMaxNumActiveViews - 1) index--;
289026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
290026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float r = (index * 1.0f) / (mMaxNumActiveViews - 2);
291026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
29236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
293026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
294026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
295026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float perspectiveTranslationY = -stackDirection * r * maxPerspectiveShiftY;
296026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float scaleShiftCorrectionY = stackDirection * (1 - scale) *
297026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR_Y) / 2.0f);
29836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float transY = perspectiveTranslationY + scaleShiftCorrectionY;
299026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
300026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float perspectiveTranslationX = (1 - r) * maxPerspectiveShiftX;
301026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float scaleShiftCorrectionX =  (1 - scale) *
302026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                (getMeasuredWidth() * (1 - PERSPECTIVE_SHIFT_FACTOR_X) / 2.0f);
30336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final float transX = perspectiveTranslationX + scaleShiftCorrectionX;
30436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
30569d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        // If this view is currently being animated for a certain position, we need to cancel
30669d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        // this animation so as not to interfere with the new transformation.
30769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        Object tag = view.getTag(com.android.internal.R.id.viewAnimation);
30869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        if (tag instanceof WeakReference<?>) {
30969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            Object obj = ((WeakReference<?>) tag).get();
31069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            if (obj instanceof ObjectAnimator) {
31169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen                ((ObjectAnimator) obj).cancel();
31269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen            }
31369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen        }
31469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen
31536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (animate) {
31636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", transX);
31736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY);
31836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder scalePropX = PropertyValuesHolder.ofFloat("scaleX", scale);
31936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            PropertyValuesHolder scalePropY = PropertyValuesHolder.ofFloat("scaleY", scale);
32036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
32136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(view, scalePropX, scalePropY,
32236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    translationY, translationX);
323c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            oa.setDuration(STACK_RELAYOUT_DURATION);
324c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen            view.setTagInternal(com.android.internal.R.id.viewAnimation,
325c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen                    new WeakReference<ObjectAnimator>(oa));
32636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            oa.start();
32736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        } else {
32836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setTranslationX(transX);
32936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setTranslationY(transY);
33036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setScaleX(scale);
33136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            view.setScaleY(scale);
33236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
333026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen    }
334026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen
3353352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private void setupStackSlider(View v, int mode) {
3363352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mStackSlider.setMode(mode);
3373352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (v != null) {
3383352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
3393352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setRotation(v.getRotation());
3403352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setTranslationY(v.getTranslationY());
341026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            mHighlight.setTranslationX(v.getTranslationX());
3423352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.bringToFront();
3433352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.bringToFront();
3443352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mStackSlider.setView(v);
3453352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3463352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.setVisibility(VISIBLE);
3473352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3483352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3493352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3503352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
3513352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
3523352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showNext() {
3530ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
3543352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
3553352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(1);
3563352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
3573352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
3583352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(0);
3593352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
3603352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
3613352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3623352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showNext();
3633352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3643352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
3653352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
3663352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
3673352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showPrevious() {
3680ac116b688380489c3690f6f65b282990c221f17Adam Cohen        if (mSwipeGestureType != GESTURE_NONE) return;
3693352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
3703352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(0);
3713352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
3723352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
3733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(1);
3743352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
3753352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
3763352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
3773352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showPrevious();
3783352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
3793352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
38096d8d56302da81b24333b204e6d7f15064538036Adam Cohen    @Override
38196d8d56302da81b24333b204e6d7f15064538036Adam Cohen    void showOnly(int childIndex, boolean animate, boolean onLayout) {
38296d8d56302da81b24333b204e6d7f15064538036Adam Cohen        super.showOnly(childIndex, animate, onLayout);
38396d8d56302da81b24333b204e6d7f15064538036Adam Cohen
38496d8d56302da81b24333b204e6d7f15064538036Adam Cohen        // Here we need to make sure that the z-order of the children is correct
3853352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
38696d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int index = modulo(i, getWindowSize());
3870ac116b688380489c3690f6f65b282990c221f17Adam Cohen            ViewAndIndex vi = mViewsMap.get(index);
3880ac116b688380489c3690f6f65b282990c221f17Adam Cohen            if (vi != null) {
3890ac116b688380489c3690f6f65b282990c221f17Adam Cohen                View v = mViewsMap.get(index).view;
3900ac116b688380489c3690f6f65b282990c221f17Adam Cohen                if (v != null) v.bringToFront();
3910ac116b688380489c3690f6f65b282990c221f17Adam Cohen            }
39296d8d56302da81b24333b204e6d7f15064538036Adam Cohen        }
3933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mTransitionIsSetup = false;
3948baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedbackIsValid = false;
3958baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
3968baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
3978baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void updateClickFeedback() {
3988baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        if (!mClickFeedbackIsValid) {
3999c295482dd739e80dd49ea0dd3102ad6be6742ddAdam Cohen            View v = getViewAtRelativeIndex(1);
4008baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (v != null) {
4018baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v,
4028baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                        HolographicHelper.CLICK_FEEDBACK));
4038baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationX(v.getTranslationX());
4048baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mClickFeedback.setTranslationY(v.getTranslationY());
4058baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
4068baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mClickFeedbackIsValid = true;
4078baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
4088baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4098baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4108baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
4118baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void showTapFeedback(View v) {
4128baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        updateClickFeedback();
4138baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(VISIBLE);
4148baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.bringToFront();
4158baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
4168baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    }
4178baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
4188baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    @Override
4198baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen    void hideTapFeedback(View v) {
4208baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        mClickFeedback.setVisibility(INVISIBLE);
4218baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        invalidate();
42296d8d56302da81b24333b204e6d7f15064538036Adam Cohen    }
42396d8d56302da81b24333b204e6d7f15064538036Adam Cohen
424f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void updateChildTransforms() {
42596d8d56302da81b24333b204e6d7f15064538036Adam Cohen        for (int i = 0; i < getNumActiveViews(); i++) {
426f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            View v = getViewAtRelativeIndex(i);
427f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            if (v != null) {
42836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                transformViewAtIndex(i, v, false);
429f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            }
430839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
433dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    @Override
434dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    FrameLayout getFrameForChild() {
435dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        FrameLayout fl = new FrameLayout(mContext);
436dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
437dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        return fl;
438dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    }
439dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
44044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Apply any necessary tranforms for the child that is being added.
44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void applyTransformForChildAtIndex(View child, int relativeIndex) {
44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
4479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void dispatchDraw(Canvas canvas) {
4480ac116b688380489c3690f6f65b282990c221f17Adam Cohen        canvas.getClipBounds(stackInvalidateRect);
449d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        final int childCount = getChildCount();
450d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        for (int i = 0; i < childCount; i++) {
451d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
4520ac116b688380489c3690f6f65b282990c221f17Adam Cohen            stackInvalidateRect.union(lp.getInvalidateRect());
453d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            lp.resetInvalidateRect();
45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
455d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.save(Canvas.CLIP_SAVE_FLAG);
4560ac116b688380489c3690f6f65b282990c221f17Adam Cohen        canvas.clipRect(stackInvalidateRect, Region.Op.UNION);
457d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        super.dispatchDraw(canvas);
458d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.restore();
4599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
46044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void onLayout() {
46244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mFirstLayoutHappened) {
463dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
464f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            updateChildTransforms();
465dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount);
46644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mFirstLayoutHappened = true;
46744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
46836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
46936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (Float.compare(mPerspectiveShiftY, mNewPerspectiveShiftY) != 0 ||
47036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                Float.compare(mPerspectiveShiftX, mNewPerspectiveShiftX) != 0) {
47136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mPerspectiveShiftY = mNewPerspectiveShiftY;
47236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mPerspectiveShiftX = mNewPerspectiveShiftX;
47336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            updateChildTransforms();
47436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
47544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
47644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
47744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
47844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onInterceptTouchEvent(MotionEvent ev) {
47944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
48044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch(action & MotionEvent.ACTION_MASK) {
48144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_DOWN: {
48244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (mActivePointerId == INVALID_POINTER) {
48344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialX = ev.getX();
48444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialY = ev.getY();
48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mActivePointerId = ev.getPointerId(0);
48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
48844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
48944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
49044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                int pointerIndex = ev.findPointerIndex(mActivePointerId);
49144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (pointerIndex == INVALID_POINTER) {
49244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    // no data for our primary pointer, this shouldn't happen, log it
49344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    Log.d(TAG, "Error: No data for our primary pointer.");
49444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    return false;
49544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
49644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float newY = ev.getY(pointerIndex);
49744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float deltaY = newY - mInitialY;
49844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
49932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
50044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
50144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
50244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
50344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
50444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
50544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
50644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP:
50744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
50844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
50944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
51044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
51144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
51244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
51344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return mSwipeGestureType != GESTURE_NONE;
51444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
51544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
51632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private void beginGestureIfNeeded(float deltaY) {
51732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
5183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
51932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            cancelLongPress();
52032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            requestDisallowInterceptTouchEvent(true);
52132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
522d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            if (mAdapter == null) return;
523d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
524839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int activeIndex;
525839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
52696d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
527839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
52896d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
529839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
53032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
5313352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            int stackMode;
5321b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen            if (mLoopViews) {
5333352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
53496d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == -1) {
53596d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex++;
5363352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.BEGINNING_OF_STACK_MODE;
53796d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount() - 1) {
5383352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.END_OF_STACK_MODE;
5393d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else {
5403352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
54132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
5423d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
5433352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE;
5443352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
5453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            View v = getViewAtRelativeIndex(activeIndex);
5463d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (v == null) return;
5473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
5483352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            setupStackSlider(v, stackMode);
5493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
5503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // We only register this gesture if we've made it this far without a problem
5513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mSwipeGestureType = swipeGestureType;
552a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen            cancelHandleClick();
55332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
55432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
55532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
55644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
55744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onTouchEvent(MotionEvent ev) {
558a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen        super.onTouchEvent(ev);
559a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen
56044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
56144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
56244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerIndex == INVALID_POINTER) {
56344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // no data for our primary pointer, this shouldn't happen, log it
56444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            Log.d(TAG, "Error: No data for our primary pointer.");
56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            return false;
56644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
56844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
56932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float newX = ev.getX(pointerIndex);
57044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float deltaY = newY - mInitialY;
57132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float deltaX = newX - mInitialX;
57244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker == null) {
57344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = VelocityTracker.obtain();
57444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
57544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mVelocityTracker.addMovement(ev);
57644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
57744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch (action & MotionEvent.ACTION_MASK) {
57844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
57932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
58032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
581dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float rx = deltaX / (mSlideAmount * 1.0f);
58232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
583dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
584839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
58532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(1 - r);
58632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
58732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
58832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
589dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
590839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
59132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(r);
59232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
59332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
59444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
59544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
59644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
59744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP: {
59844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                handlePointerUp(ev);
59944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
60044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
60144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
60244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
60344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
60444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
60544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
60644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
60744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
60844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
60944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
61044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
61144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return true;
61244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
61344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
61444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void onSecondaryPointerUp(MotionEvent ev) {
61544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int activePointerIndex = ev.getActionIndex();
61644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int pointerId = ev.getPointerId(activePointerIndex);
61744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerId == mActivePointerId) {
61844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
61996d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
62044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
62144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            View v = getViewAtRelativeIndex(activeViewIndex);
62244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (v == null) return;
62344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
62444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Our primary pointer has gone up -- let's see if we can find
62544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // another pointer on the view. If so, then we should replace
62644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // our primary pointer with this new pointer and adjust things
62744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // so that the view doesn't jump
62844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            for (int index = 0; index < ev.getPointerCount(); index++) {
62944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (index != activePointerIndex) {
63044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
63144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float x = ev.getX(index);
63244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float y = ev.getY(index);
63344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
634e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
635e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                    if (mTouchRect.contains(Math.round(x), Math.round(y))) {
63644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldX = ev.getX(activePointerIndex);
63744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldY = ev.getY(activePointerIndex);
63844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
63944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // adjust our frame of reference to avoid a jump
64044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialY += (y - oldY);
64144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialX += (x - oldX);
64244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
64344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mActivePointerId = ev.getPointerId(index);
64444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        if (mVelocityTracker != null) {
64544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                            mVelocityTracker.clear();
64644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        }
64744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // ok, we're good, we found a new pointer which is touching the active view
64844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        return;
64944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    }
65044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
65144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
65244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
6533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // so end the gesture
65444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            handlePointerUp(ev);
65544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
65644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
65744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
65844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void handlePointerUp(MotionEvent ev) {
65944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
66044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
66144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int deltaY = (int) (newY - mInitialY);
66226e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        mLastInteractionTime = System.currentTimeMillis();
66344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (mVelocityTracker != null) {
6653d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
6663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
6673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
66844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
66944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker != null) {
67044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker.recycle();
67144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = null;
67244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
67344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
6753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
6760ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
6770ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
6780ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
6790ac116b688380489c3690f6f65b282990c221f17Adam Cohen
68044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe down
681839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
682839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showPrevious();
68396d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
68496d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showNext();
685839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
68632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
6873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
6883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
6890ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // We reset the gesture variable, because otherwise we will ignore showPrevious() /
6900ac116b688380489c3690f6f65b282990c221f17Adam Cohen            // showNext();
6910ac116b688380489c3690f6f65b282990c221f17Adam Cohen            mSwipeGestureType = GESTURE_NONE;
6920ac116b688380489c3690f6f65b282990c221f17Adam Cohen
69344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe up
694839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
695839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showNext();
69696d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
69796d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showPrevious();
698839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
699839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
70032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
701839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
70244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe up far enough, snap back down
703839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
704839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
705839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
706839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
707839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
708839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
709839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
71032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
711b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
7122794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress);
7132794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
7142794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
715839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
7162794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
717839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
718839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
71932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
72044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe down far enough, snap back up
721839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
722839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
723839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
724839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
725839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
726839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
727839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
7283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
729b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
7302794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY =
7312794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    PropertyValuesHolder.ofFloat("YProgress",finalYProgress);
7322794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
7332794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
734839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
7352794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
736839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
73744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
73844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
73944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
74044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mSwipeGestureType = GESTURE_NONE;
74132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
74232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
74332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private class StackSlider {
74432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        View mView;
74532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mYProgress;
74632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mXProgress;
74732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int NORMAL_MODE = 0;
7493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int BEGINNING_OF_STACK_MODE = 1;
7503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int END_OF_STACK_MODE = 2;
7513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
7523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        int mMode = NORMAL_MODE;
7533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
754b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider() {
755b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
756b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
757b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider(StackSlider copy) {
758b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mView = copy.mView;
759b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mYProgress = copy.mYProgress;
760b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mXProgress = copy.mXProgress;
7613d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = copy.mMode;
762b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
763b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
76432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float cubic(float r) {
765839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
76632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
76732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
76832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float highlightAlphaInterpolator(float r) {
76932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.4f;
77032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r < pivot) {
771839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(r / pivot);
77232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
773839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
77432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
77532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
77632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
77732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float viewAlphaInterpolator(float r) {
77832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.3f;
77932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r > pivot) {
780839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
78132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
78232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0;
78332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
78432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
78532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
786b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        private float rotationInterpolator(float r) {
787b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            float pivot = 0.2f;
788b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            if (r < pivot) {
789b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return 0;
790b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            } else {
791839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
792b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
793b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
794b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
79532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        void setView(View v) {
79632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mView = v;
79732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
79832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
79932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setYProgress(float r) {
80032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
80132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.min(1.0f, r);
80232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.max(0, r);
80332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
80432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mYProgress = r;
805a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
806a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen
80732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
80832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
80932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
810839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
811839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
812488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            // We need to prevent any clipping issues which may arise by setting a layer type.
813488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            // This doesn't come for free however, so we only want to enable it when required.
814488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            if (Float.compare(0f, mYProgress) != 0 && Float.compare(1.0f, mYProgress) != 0) {
815488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                if (mView.getLayerType() == LAYER_TYPE_NONE) {
816488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                    mView.setLayerType(LAYER_TYPE_HARDWARE, null);
817488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                }
818488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            } else {
819488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                if (mView.getLayerType() != LAYER_TYPE_NONE) {
820488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                    mView.setLayerType(LAYER_TYPE_NONE, null);
821488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen                }
822488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen            }
823488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen
8243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            switch (mMode) {
8253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case NORMAL_MODE:
826dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
827dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
8283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
8293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
830839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float alpha = viewAlphaInterpolator(1 - r);
8313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
8323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
8333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // so that they don't interfere with click events.
8343d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
8353d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(VISIBLE);
8363d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    } else if (alpha == 0 && mView.getAlpha() != 0
8373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                            && mView.getVisibility() == VISIBLE) {
8383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(INVISIBLE);
8393d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    }
840b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
8413d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setAlpha(alpha);
842839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
843839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
8443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
84596d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case END_OF_STACK_MODE:
846839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = r * 0.2f;
847dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
848dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
8493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
8503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
85196d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case BEGINNING_OF_STACK_MODE:
852839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = (1-r) * 0.2f;
853dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
854dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
8553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
8563d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
857b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
85832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
85932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
86032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setXProgress(float r) {
86132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
8623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.min(2.0f, r);
8633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.max(-2.0f, r);
86432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
86532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mXProgress = r;
86632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
867a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen            if (mView == null) return;
86832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
86932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
87032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r *= 0.2f;
872dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
873dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
87432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
87532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        void setMode(int mode) {
8773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = mode;
8783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
8793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
8803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForNeutralPosition() {
881839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, 0);
8823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
8833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
8843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForOffscreenPosition() {
885839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, 0);
886839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
887839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
888839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForNeutralPosition(float velocity) {
889839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, velocity);
890839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
891839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
892839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForOffscreenPosition(float velocity) {
893839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, velocity);
8943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
8953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
896839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private float getDuration(boolean invert, float velocity) {
8973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mView != null) {
8983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
8993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
900839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
901839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        Math.pow(viewLp.verticalOffset, 2));
902dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
903dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                        Math.pow(0.4f * mSlideAmount, 2));
904839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
905839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                if (velocity == 0) {
906839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
907839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                } else {
908839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float duration = invert ? d / Math.abs(velocity) :
909839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            (maxd - d) / Math.abs(velocity);
910839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (duration < MINIMUM_ANIMATION_DURATION ||
911839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            duration > DEFAULT_ANIMATION_DURATION) {
912839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return getDuration(invert, 0);
913839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    } else {
914839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return duration;
915839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    }
916839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                }
9173d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            }
9183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return 0;
9193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
9203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
921e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
922e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
923839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getYProgress() {
92432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mYProgress;
92532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
92632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
927e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
928e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
929839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getXProgress() {
93032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mXProgress;
93132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
93244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
93344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
93444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
93544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public void onRemoteAdapterConnected() {
93644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super.onRemoteAdapterConnected();
9371480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // On first run, we want to set the stack to the end.
93896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        if (mWhichChild == -1) {
93996d8d56302da81b24333b204e6d7f15064538036Adam Cohen            mWhichChild = 0;
9403042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen        }
94196d8d56302da81b24333b204e6d7f15064538036Adam Cohen        setDisplayedChild(mWhichChild);
94244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
94332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    LayoutParams createOrReuseLayoutParams(View v) {
9459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
9469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (currentLp instanceof LayoutParams) {
9479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) currentLp;
9489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setHorizontalOffset(0);
9499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setVerticalOffset(0);
950839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
951839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
9529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return lp;
9539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        return new LayoutParams(v);
95532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
95632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    @Override
9589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
9599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        boolean dataChanged = mDataChanged;
9609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (dataChanged) {
9619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            handleDataChanged();
9629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // if the data changes, mWhichChild might be out of the bounds of the adapter
9649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // in this case, we reset mWhichChild to the beginning
9659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mWhichChild >= mAdapter.getCount())
9669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mWhichChild = 0;
9679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            showOnly(mWhichChild, true, true);
9696364f2bbe5254b4274f3feffc48f4259eacc205eWinson Chung            refreshChildren();
9709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final int childCount = getChildCount();
9739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        for (int i = 0; i < childCount; i++) {
9749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            final View child = getChildAt(i);
9759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childRight = mPaddingLeft + child.getMeasuredWidth();
9779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childBottom = mPaddingTop + child.getMeasuredHeight();
9789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
9799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
9819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
9829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        mDataChanged = false;
9869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        onLayout();
9879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
9889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
98926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    @Override
99026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    public void advance() {
99126e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime;
99226e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        if (mSwipeGestureType == GESTURE_NONE &&
99326e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen                timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) {
99426e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen            showNext();
99526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen        }
99626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen    }
99726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen
998839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private void measureChildren() {
999839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int count = getChildCount();
100036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
100136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int measuredWidth = getMeasuredWidth();
100236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int measuredHeight = getMeasuredHeight();
100336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
100436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int childWidth = Math.round(measuredWidth*(1-PERSPECTIVE_SHIFT_FACTOR_X))
1005026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                - mPaddingLeft - mPaddingRight;
100636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        final int childHeight = Math.round(measuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR_Y))
1007839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                - mPaddingTop - mPaddingBottom;
1008839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
100936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        int maxWidth = 0;
101036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        int maxHeight = 0;
101136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
1012839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        for (int i = 0; i < count; i++) {
1013839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            final View child = getChildAt(i);
101436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST),
101536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
101636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
101736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            if (child != mHighlight && child != mClickFeedback) {
101836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                final int childMeasuredWidth = child.getMeasuredWidth();
101936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                final int childMeasuredHeight = child.getMeasuredHeight();
102036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                if (childMeasuredWidth > maxWidth) {
102136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    maxWidth = childMeasuredWidth;
102236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                }
102336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                if (childMeasuredHeight > maxHeight) {
102436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                    maxHeight = childMeasuredHeight;
102536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen                }
102636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            }
102736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
102836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
102936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        mNewPerspectiveShiftX = PERSPECTIVE_SHIFT_FACTOR_X * measuredWidth;
103036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        mNewPerspectiveShiftY = PERSPECTIVE_SHIFT_FACTOR_Y * measuredHeight;
103136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (maxWidth > 0 && maxWidth < childWidth) {
103236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mNewPerspectiveShiftX = measuredWidth - maxWidth;
103336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        }
103436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen
103536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen        if (maxHeight > 0 && maxHeight < childHeight) {
103636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen            mNewPerspectiveShiftY = measuredHeight - maxHeight;
1037839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1038839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
1039839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1040839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    @Override
1041839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1042839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
1043839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
1044839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
1045839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
1046839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1047839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
1048839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1049839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // We need to deal with the case where our parent hasn't told us how
1050839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // big we should be. In this case we should
1051026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorY = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_Y);
1052839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
1053839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ?
1054026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildHeight * (1 + factorY)) +
1055839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingTop + mPaddingBottom : 0;
1056839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
1057189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
1058026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                int height = Math.round(mReferenceChildHeight * (1 + factorY))
1059189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                        + mPaddingTop + mPaddingBottom;
1060189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (height <= heightSpecSize) {
1061189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize = height;
1062189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
1063189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    heightSpecSize |= MEASURED_STATE_TOO_SMALL;
1064189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
1065189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
1066189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                heightSpecSize = 0;
1067189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
1068839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1069839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1070026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen        float factorX = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_X);
1071839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
1072026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            widthSpecSize = haveChildRefSize ?
1073026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    Math.round(mReferenceChildWidth * (1 + factorX)) +
1074026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen                    mPaddingLeft + mPaddingRight : 0;
1075839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
1076189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            if (haveChildRefSize) {
1077189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
1078189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                if (width <= widthSpecSize) {
1079189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize = width;
1080189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                } else {
1081189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                    widthSpecSize |= MEASURED_STATE_TOO_SMALL;
1082189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                }
1083189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            } else {
1084189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn                widthSpecSize = 0;
1085189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            }
1086839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
1087839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1088839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        setMeasuredDimension(widthSpecSize, heightSpecSize);
1089839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        measureChildren();
1090839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
1091839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
10929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    class LayoutParams extends ViewGroup.LayoutParams {
10939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int horizontalOffset;
10949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int verticalOffset;
10959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        View mView;
1096d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect parentRect = new Rect();
1097d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect invalidateRect = new Rect();
1098d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final RectF invalidateRectf = new RectF();
1099d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect globalInvalidateRect = new Rect();
11009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(View view) {
11029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(0, 0);
1103839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1104839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
11059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
11069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
11079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mView = view;
11089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
11099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(Context c, AttributeSet attrs) {
11119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(c, attrs);
11129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
11139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
1114839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
1115839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
1116b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
1117b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
11189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void invalidateGlobalRegion(View v, Rect r) {
1119d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            // We need to make a new rect here, so as not to modify the one passed
1120d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            globalInvalidateRect.set(r);
11219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View p = v;
11229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
11239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            boolean firstPass = true;
11259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            parentRect.set(0, 0, 0, 0);
11269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int depth = 0;
1127b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen            while (p.getParent() != null && p.getParent() instanceof View
1128d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    && !parentRect.contains(globalInvalidateRect)) {
11299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (!firstPass) {
1130d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
1131d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                            - p.getScrollY());
11329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    depth++;
11339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                }
11349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                firstPass = false;
11359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                p = (View) p.getParent();
1136b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                parentRect.set(p.getScrollX(), p.getScrollY(),
1137b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                               p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
1138839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
11399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
11409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1141d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top,
1142d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.right, globalInvalidateRect.bottom);
1143d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
1144d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
1145d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        Rect getInvalidateRect() {
1146d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            return invalidateRect;
1147d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
11489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1149d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        void resetInvalidateRect() {
11500ac116b688380489c3690f6f65b282990c221f17Adam Cohen            invalidateRect.set(0, 0, 0, 0);
11519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
115232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1153a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase        // This is public so that ObjectAnimator can access it
11549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setVerticalOffset(int newVerticalOffset) {
11550ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(horizontalOffset, newVerticalOffset);
11569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
11579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setHorizontalOffset(int newHorizontalOffset) {
11590ac116b688380489c3690f6f65b282990c221f17Adam Cohen            setOffsets(newHorizontalOffset, verticalOffset);
11600ac116b688380489c3690f6f65b282990c221f17Adam Cohen        }
11610ac116b688380489c3690f6f65b282990c221f17Adam Cohen
11620ac116b688380489c3690f6f65b282990c221f17Adam Cohen        public void setOffsets(int newHorizontalOffset, int newVerticalOffset) {
11630ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset;
11649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = newHorizontalOffset;
11650ac116b688380489c3690f6f65b282990c221f17Adam Cohen            int verticalOffsetDelta = newVerticalOffset - verticalOffset;
11660ac116b688380489c3690f6f65b282990c221f17Adam Cohen            verticalOffset = newVerticalOffset;
11679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
11699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
11700ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft());
11710ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight());
11720ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop());
11730ac116b688380489c3690f6f65b282990c221f17Adam Cohen                int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom());
11740ac116b688380489c3690f6f65b282990c221f17Adam Cohen
11750ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRectf.set(left, top, right, bottom);
11769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
11789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
11799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
11809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
11819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
11829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11830ac116b688380489c3690f6f65b282990c221f17Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
11849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
11859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
11869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
11879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
11889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
11899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
11909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
119132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
119232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
11939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static class HolographicHelper {
11949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mHolographicPaint = new Paint();
11959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mErasePaint = new Paint();
1196839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private final Paint mBlurPaint = new Paint();
11978baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int RES_OUT = 0;
11988baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private static final int CLICK_FEEDBACK = 1;
11998baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        private float mDensity;
1200e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mSmallBlurMaskFilter;
1201e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private BlurMaskFilter mLargeBlurMaskFilter;
1202e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mCanvas = new Canvas();
1203e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Canvas mMaskCanvas = new Canvas();
1204e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final int[] mTmpXY = new int[2];
1205e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy        private final Matrix mIdentityMatrix = new Matrix();
120632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1207dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        HolographicHelper(Context context) {
12088baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            mDensity = context.getResources().getDisplayMetrics().density;
1209dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
12109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setFilterBitmap(true);
1211839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
12129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
12139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setFilterBitmap(true);
1214e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy
1215e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL);
1216e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL);
12179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
12189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        Bitmap createOutline(View v) {
12208baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            return createOutline(v, RES_OUT);
12218baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        }
12228baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
12238baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen        Bitmap createOutline(View v, int type) {
12248baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            if (type == RES_OUT) {
12258baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mHolographicPaint.setColor(0xff6699ff);
1226e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mSmallBlurMaskFilter);
12278baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            } else if (type == CLICK_FEEDBACK) {
12288baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen                mHolographicPaint.setColor(0x886699ff);
1229e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy                mBlurPaint.setMaskFilter(mLargeBlurMaskFilter);
12308baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen            }
12318baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen
12329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
12339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                return null;
12349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
12359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
12379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    Bitmap.Config.ARGB_8888);
1238e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mCanvas.setBitmap(bitmap);
123932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
12409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float rotationX = v.getRotationX();
1241839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float rotation = v.getRotation();
1242839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float translationY = v.getTranslationY();
1243026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            float translationX = v.getTranslationX();
12449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(0);
1245839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(0);
1246839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(0);
1247026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(0);
1248e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            v.draw(mCanvas);
12499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(rotationX);
1250839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(rotation);
1251839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(translationY);
1252026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen            v.setTranslationX(translationX);
12539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1254e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            drawOutline(mCanvas, bitmap);
12559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return bitmap;
12569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
12579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
12589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void drawOutline(Canvas dest, Bitmap src) {
1259e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            final int[] xy = mTmpXY;
1260839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
1261e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.setBitmap(mask);
1262e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
12639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawColor(0, PorterDuff.Mode.CLEAR);
1264e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy            dest.setMatrix(mIdentityMatrix);
1265839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
12669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mask.recycle();
12679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
126832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
126944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen}
1270