StackView.java revision a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20
144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/*
244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Copyright (C) 2010 The Android Open Source Project
344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Licensed under the Apache License, Version 2.0 (the "License");
544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * you may not use this file except in compliance with the License.
644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * You may obtain a copy of the License at
744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *      http://www.apache.org/licenses/LICENSE-2.0
944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen *
1044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Unless required by applicable law or agreed to in writing, software
1144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * distributed under the License is distributed on an "AS IS" BASIS,
1244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * See the License for the specific language governing permissions and
1444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * limitations under the License.
1544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
1644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpackage android.widget;
1844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
19a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator;
20e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guyimport android.animation.PropertyValuesHolder;
2144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context;
2232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap;
23839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter;
2432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas;
2532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix;
2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint;
2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff;
2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode;
2944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect;
309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF;
31d51bbb5b56446519db88f49f2682da782b0069acAdam Cohenimport android.graphics.Region;
32839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter;
3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet;
3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log;
3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent;
3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker;
3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View;
3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration;
3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup;
40b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator;
4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView;
4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView
4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/**
4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe
4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children.
4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator {
4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final String TAG = "StackView";
5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Default animation parameters
5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
54e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int DEFAULT_ANIMATION_DURATION = 400;
55e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    private static final int MINIMUM_ANIMATION_DURATION = 50;
5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
58839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Parameters effecting the perspective visuals
59839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
60839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f;
61e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy    @SuppressWarnings({"FieldCanBeLocal"})
62839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static float PERSPECTIVE_SCALE_FACTOR = 0.35f;
63839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
64839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
65839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Represent the two possible stack modes, one where items slide up, and the other
66839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * where items slide down. The perspective is also inverted between these two modes.
67839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
68839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_UP = 0;
69839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_DOWN = 1;
70839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
71839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
7244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These specify the different gesture states
7344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
745b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_NONE = 0;
755b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_UP = 1;
765b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_DOWN = 2;
7744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
7844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
7944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Specifies how far you need to swipe (up or down) before it
8044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * will be consider a completed gesture when you lift your finger
8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
82a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    private static final float SWIPE_THRESHOLD_RATIO = 0.2f;
83a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen
84a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen    /**
85a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * Specifies the total distance, relative to the size of the stack,
86a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     * that views will be slid, either up or down
87a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen     */
885b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SLIDE_UP_RATIO = 0.7f;
8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Sentinel value for no current active pointer.
9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Used by {@link #mActivePointerId}.
9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
9444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private static final int INVALID_POINTER = -1;
9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
9644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
97839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
98839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
99839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int NUM_ACTIVE_VIEWS = 5;
100839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
101dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private static final int FRAME_PADDING = 4;
102839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
103839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These variables are all related to the current state of touch interaction
10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * with the stack
10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialY;
10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialX;
10944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mActivePointerId;
11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mYVelocity = 0;
11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeGestureType = GESTURE_NONE;
112dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mSlideAmount;
11344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeThreshold;
11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mTouchSlop;
11544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mMaximumVelocity;
11644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private VelocityTracker mVelocityTracker;
1173352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private boolean mTransitionIsSetup = false;
11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static HolographicHelper sHolographicHelper;
12032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private ImageView mHighlight;
12132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private StackSlider mStackSlider;
12244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private boolean mFirstLayoutHappened = false;
123839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private int mStackMode;
124dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mFramePadding;
125d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen    private final Rect invalidateRect = new Rect();
12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context) {
12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context);
12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context, AttributeSet attrs) {
13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context, attrs);
13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void initStackView() {
13896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        configureViewAnimator(NUM_ACTIVE_VIEWS, 1);
13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        setStaticTransformationsEnabled(true);
14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
141b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        mTouchSlop = configuration.getScaledTouchSlop();
14244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
14432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
14532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight = new ImageView(getContext());
14632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
14732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
14832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mStackSlider = new StackSlider();
14932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (sHolographicHelper == null) {
151dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            sHolographicHelper = new HolographicHelper(mContext);
15232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
1539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipChildren(false);
1549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipToPadding(false);
1551480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen
156839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // This sets the form of the StackView, which is currently to have the perspective-shifted
157839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // views above the active view, and have items slide down when sliding out. The opposite is
158839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // available by using ITEMS_SLIDE_UP.
159839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        mStackMode = ITEMS_SLIDE_DOWN;
160839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1611480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // This is a flag to indicate the the stack is loading for the first time
1621480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        mWhichChild = -1;
163dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
164dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // Adjust the frame padding based on the density, since the highlight changes based
165dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // on the density
166dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        final float density = mContext.getResources().getDisplayMetrics().density;
167dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
16844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
16944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
17044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
17144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
17244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
17344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void animateViewForTransition(int fromIndex, int toIndex, View view) {
17496d8d56302da81b24333b204e6d7f15064538036Adam Cohen        if (fromIndex == -1 && toIndex != 0) {
17544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item in
17644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (view.getAlpha() == 1) {
17744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                view.setAlpha(0);
17844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
179b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(VISIBLE);
180b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
1812794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f);
1822794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            fadeIn.setDuration(DEFAULT_ANIMATION_DURATION);
18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            fadeIn.start();
18496d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 0 && toIndex == 1) {
18544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item in
18644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setVisibility(VISIBLE);
18732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
188839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
18944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
190b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
1912794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f);
1922794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
1932794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
194839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    slideInX, slideInY);
1952794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
196839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
197839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
19896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == 1 && toIndex == 0) {
19944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item out
200839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
20144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
202b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
2032794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f);
2042794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
2052794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
2062794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    slideOutX, slideOutY);
2072794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
208839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
209839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
21096d8d56302da81b24333b204e6d7f15064538036Adam Cohen        } else if (fromIndex == -1 && toIndex == 0) {
21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Make sure this view that is "waiting in the wings" is invisible
21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setAlpha(0.0f);
213b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(INVISIBLE);
214b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
215dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            lp.setVerticalOffset(-mSlideAmount);
21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (toIndex == -1) {
21744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item out
2182794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f);
2192794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            fadeOut.setDuration(DEFAULT_ANIMATION_DURATION);
22044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            fadeOut.start();
22144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
222839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
223839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // Implement the faked perspective
224839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (toIndex != -1) {
225f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            transformViewAtIndex(toIndex, view);
226f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        }
227f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    }
228839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
2293352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    private void setupStackSlider(View v, int mode) {
2303352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mStackSlider.setMode(mode);
2313352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (v != null) {
2323352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
2333352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setRotation(v.getRotation());
2343352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.setTranslationY(v.getTranslationY());
2353352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mHighlight.bringToFront();
2363352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.bringToFront();
2373352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mStackSlider.setView(v);
2383352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
2393352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            v.setVisibility(VISIBLE);
2403352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
2413352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
2423352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
2433352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
2443352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
2453352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showNext() {
2463352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
2473352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(1);
2483352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
2493352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
2503352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(0);
2513352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
2523352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
2533352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
2543352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showNext();
2553352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
2563352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
2573352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @Override
2583352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    @android.view.RemotableViewMethod
2593352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    public void showPrevious() {
2603352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        if (!mTransitionIsSetup) {
2613352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            View v = getViewAtRelativeIndex(0);
2623352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            if (v != null) {
2633352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                setupStackSlider(v, StackSlider.NORMAL_MODE);
2643352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setYProgress(1);
2653352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                mStackSlider.setXProgress(0);
2663352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            }
2673352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        }
2683352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        super.showPrevious();
2693352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen    }
2703352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
271f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void transformViewAtIndex(int index, View view) {
272dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float maxPerpectiveShift = mMeasuredHeight * PERSPECTIVE_SHIFT_FACTOR;
273839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
27496d8d56302da81b24333b204e6d7f15064538036Adam Cohen        index = mMaxNumActiveViews - index - 1;
27596d8d56302da81b24333b204e6d7f15064538036Adam Cohen        if (index == mMaxNumActiveViews - 1) index--;
276839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
27796d8d56302da81b24333b204e6d7f15064538036Adam Cohen        float r = (index * 1.0f) / (mMaxNumActiveViews - 2);
278839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
279f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
2802794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", scale);
2812794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", scale);
282839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
283f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        r = (float) Math.pow(r, 2);
284839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
285f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
286dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float perspectiveTranslation = -stackDirection * r * maxPerpectiveShift;
287dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float scaleShiftCorrection = stackDirection * (1 - scale) *
288dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                (mMeasuredHeight * (1 - PERSPECTIVE_SHIFT_FACTOR) / 2.0f);
289dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float transY = perspectiveTranslation + scaleShiftCorrection;
290f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen
2912794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY);
2922794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY, translationY);
2932794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        pa.setDuration(100);
294f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        pa.start();
295f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    }
296f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen
29796d8d56302da81b24333b204e6d7f15064538036Adam Cohen    @Override
29896d8d56302da81b24333b204e6d7f15064538036Adam Cohen    void showOnly(int childIndex, boolean animate, boolean onLayout) {
29996d8d56302da81b24333b204e6d7f15064538036Adam Cohen        super.showOnly(childIndex, animate, onLayout);
30096d8d56302da81b24333b204e6d7f15064538036Adam Cohen
30196d8d56302da81b24333b204e6d7f15064538036Adam Cohen        // Here we need to make sure that the z-order of the children is correct
3023352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
30396d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int index = modulo(i, getWindowSize());
30496d8d56302da81b24333b204e6d7f15064538036Adam Cohen            View v = mViewsMap.get(index).view;
30596d8d56302da81b24333b204e6d7f15064538036Adam Cohen            if (v != null) v.bringToFront();
30696d8d56302da81b24333b204e6d7f15064538036Adam Cohen        }
3073352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen        mTransitionIsSetup = false;
30896d8d56302da81b24333b204e6d7f15064538036Adam Cohen    }
30996d8d56302da81b24333b204e6d7f15064538036Adam Cohen
310f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void updateChildTransforms() {
31196d8d56302da81b24333b204e6d7f15064538036Adam Cohen        for (int i = 0; i < getNumActiveViews(); i++) {
312f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            View v = getViewAtRelativeIndex(i);
313f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            if (v != null) {
314f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen                transformViewAtIndex(i, v);
315f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            }
316839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
31744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
31844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
319dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    @Override
320dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    FrameLayout getFrameForChild() {
321dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        FrameLayout fl = new FrameLayout(mContext);
322dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
323dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        return fl;
324dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    }
325dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
32644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
32744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Apply any necessary tranforms for the child that is being added.
32844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
32944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void applyTransformForChildAtIndex(View child, int relativeIndex) {
33044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
33144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
33244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
3339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void dispatchDraw(Canvas canvas) {
334d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.getClipBounds(invalidateRect);
335d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        final int childCount = getChildCount();
336d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        for (int i = 0; i < childCount; i++) {
337d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
338d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            invalidateRect.union(lp.getInvalidateRect());
339d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            lp.resetInvalidateRect();
34044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
341d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
342d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.save(Canvas.CLIP_SAVE_FLAG);
343d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.clipRect(invalidateRect, Region.Op.UNION);
344d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        super.dispatchDraw(canvas);
345d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        canvas.restore();
3469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
34744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
3489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void onLayout() {
34944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mFirstLayoutHappened) {
350dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
351f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            updateChildTransforms();
352dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount);
35344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mFirstLayoutHappened = true;
35444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
35544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
35644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
35744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
35844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onInterceptTouchEvent(MotionEvent ev) {
35944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
36044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch(action & MotionEvent.ACTION_MASK) {
36144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_DOWN: {
36244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (mActivePointerId == INVALID_POINTER) {
36344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialX = ev.getX();
36444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialY = ev.getY();
36544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mActivePointerId = ev.getPointerId(0);
36644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
36744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
36844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
36944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
37044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                int pointerIndex = ev.findPointerIndex(mActivePointerId);
37144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (pointerIndex == INVALID_POINTER) {
37244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    // no data for our primary pointer, this shouldn't happen, log it
37344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    Log.d(TAG, "Error: No data for our primary pointer.");
37444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    return false;
37544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
37644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float newY = ev.getY(pointerIndex);
37744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float deltaY = newY - mInitialY;
37844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
37932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
38044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
38144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
38244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
38344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
38444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
38544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
38644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP:
38744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
38844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
38944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
39044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
39144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
39344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return mSwipeGestureType != GESTURE_NONE;
39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
39544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
39632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private void beginGestureIfNeeded(float deltaY) {
39732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
3983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
39932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            cancelLongPress();
40032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            requestDisallowInterceptTouchEvent(true);
40132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
402d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            if (mAdapter == null) return;
403d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
404839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int activeIndex;
405839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
40696d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
407839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
40896d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
409839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
41032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
4113352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            int stackMode;
4121b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen            if (mLoopViews) {
4133352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
41496d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == -1) {
41596d8d56302da81b24333b204e6d7f15064538036Adam Cohen                activeIndex++;
4163352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.BEGINNING_OF_STACK_MODE;
41796d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount() - 1) {
4183352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.END_OF_STACK_MODE;
4193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else {
4203352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen                stackMode = StackSlider.NORMAL_MODE;
42132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
4223d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
4233352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE;
4243352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen
4253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            View v = getViewAtRelativeIndex(activeIndex);
4263d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (v == null) return;
4273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
4283352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen            setupStackSlider(v, stackMode);
4293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
4303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // We only register this gesture if we've made it this far without a problem
4313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mSwipeGestureType = swipeGestureType;
432a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen            cancelHandleClick();
43332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
43432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
43532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
43644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
43744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onTouchEvent(MotionEvent ev) {
438a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen        super.onTouchEvent(ev);
439a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen
44044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerIndex == INVALID_POINTER) {
44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // no data for our primary pointer, this shouldn't happen, log it
44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            Log.d(TAG, "Error: No data for our primary pointer.");
44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            return false;
44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
44744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
44844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
44932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float newX = ev.getX(pointerIndex);
45044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float deltaY = newY - mInitialY;
45132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float deltaX = newX - mInitialX;
45244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker == null) {
45344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = VelocityTracker.obtain();
45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
45544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mVelocityTracker.addMovement(ev);
45644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
45744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch (action & MotionEvent.ACTION_MASK) {
45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
45932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
46032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
461dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float rx = deltaX / (mSlideAmount * 1.0f);
46232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
463dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
464839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
46532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(1 - r);
46632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
46732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
46832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
469dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
470839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
47132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(r);
47232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
47332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
47444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
47544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
47644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
47744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP: {
47844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                handlePointerUp(ev);
47944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
48044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
48144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
48244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
48344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
48444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
48844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
48944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
49044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
49144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return true;
49244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
49344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
49444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final Rect touchRect = new Rect();
49544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void onSecondaryPointerUp(MotionEvent ev) {
49644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int activePointerIndex = ev.getActionIndex();
49744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int pointerId = ev.getPointerId(activePointerIndex);
49844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerId == mActivePointerId) {
49944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
50096d8d56302da81b24333b204e6d7f15064538036Adam Cohen            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
50144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
50244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            View v = getViewAtRelativeIndex(activeViewIndex);
50344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (v == null) return;
50444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
50544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Our primary pointer has gone up -- let's see if we can find
50644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // another pointer on the view. If so, then we should replace
50744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // our primary pointer with this new pointer and adjust things
50844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // so that the view doesn't jump
50944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            for (int index = 0; index < ev.getPointerCount(); index++) {
51044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (index != activePointerIndex) {
51144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
51244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float x = ev.getX(index);
51344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float y = ev.getY(index);
51444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
51544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
5165b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy                    if (touchRect.contains(Math.round(x), Math.round(y))) {
51744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldX = ev.getX(activePointerIndex);
51844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldY = ev.getY(activePointerIndex);
51944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
52044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // adjust our frame of reference to avoid a jump
52144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialY += (y - oldY);
52244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialX += (x - oldX);
52344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
52444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mActivePointerId = ev.getPointerId(index);
52544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        if (mVelocityTracker != null) {
52644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                            mVelocityTracker.clear();
52744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        }
52844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // ok, we're good, we found a new pointer which is touching the active view
52944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        return;
53044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    }
53144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
53244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
53344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
5343d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // so end the gesture
53544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            handlePointerUp(ev);
53644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
53744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
53844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
53944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void handlePointerUp(MotionEvent ev) {
54044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
54144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
54244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int deltaY = (int) (newY - mInitialY);
54344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (mVelocityTracker != null) {
5453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
5463d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
5473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
54844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
54944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker != null) {
55044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker.recycle();
55144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = null;
55244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
55344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
5553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
55644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe down
557839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
558839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showPrevious();
55996d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
56096d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showNext();
561839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
56232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
5633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
5643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe up
566839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
567839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showNext();
56896d8d56302da81b24333b204e6d7f15064538036Adam Cohen            } else {
56996d8d56302da81b24333b204e6d7f15064538036Adam Cohen                showPrevious();
570839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
571839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
57232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
573839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
57444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe up far enough, snap back down
575839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
576839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
577839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
578839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
579839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
580839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
581839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
58232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
583b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
5842794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress);
5852794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
5862794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
587839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
5882794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
589839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
590839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
59132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
59244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe down far enough, snap back up
593839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
594839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
595839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
596839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
597839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
598839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
599839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
6003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
601b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
6022794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY =
6032794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    PropertyValuesHolder.ofFloat("YProgress",finalYProgress);
6042794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
6052794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
606839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
6072794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
608839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
60944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
61044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
61144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
61244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mSwipeGestureType = GESTURE_NONE;
61332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
61432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
61532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private class StackSlider {
61632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        View mView;
61732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mYProgress;
61832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mXProgress;
61932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int NORMAL_MODE = 0;
6213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int BEGINNING_OF_STACK_MODE = 1;
6223d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int END_OF_STACK_MODE = 2;
6233d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        int mMode = NORMAL_MODE;
6253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
626b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider() {
627b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
628b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
629b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider(StackSlider copy) {
630b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mView = copy.mView;
631b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mYProgress = copy.mYProgress;
632b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mXProgress = copy.mXProgress;
6333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = copy.mMode;
634b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
635b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
63632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float cubic(float r) {
637839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
63832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
63932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
64032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float highlightAlphaInterpolator(float r) {
64132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.4f;
64232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r < pivot) {
643839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(r / pivot);
64432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
645839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
64632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
64732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
64832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
64932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float viewAlphaInterpolator(float r) {
65032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.3f;
65132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r > pivot) {
652839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
65332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
65432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0;
65532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
65632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
65732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
658b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        private float rotationInterpolator(float r) {
659b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            float pivot = 0.2f;
660b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            if (r < pivot) {
661b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return 0;
662b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            } else {
663839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
664b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
665b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
666b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
66732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        void setView(View v) {
66832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mView = v;
66932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
67032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
67132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setYProgress(float r) {
67232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
67332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.min(1.0f, r);
67432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.max(0, r);
67532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
67632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mYProgress = r;
67732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
67832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
67932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
680839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
681839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
6823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            switch (mMode) {
6833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case NORMAL_MODE:
684dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
685dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
6863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
6873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
688839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float alpha = viewAlphaInterpolator(1 - r);
6893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
6913d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // so that they don't interfere with click events.
6923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
6933d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(VISIBLE);
6943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    } else if (alpha == 0 && mView.getAlpha() != 0
6953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                            && mView.getVisibility() == VISIBLE) {
6963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(INVISIBLE);
6973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    }
698b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
6993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setAlpha(alpha);
700839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
701839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
7023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
70396d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case END_OF_STACK_MODE:
704839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = r * 0.2f;
705dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
706dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
7073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
7083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
70996d8d56302da81b24333b204e6d7f15064538036Adam Cohen                case BEGINNING_OF_STACK_MODE:
710839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = (1-r) * 0.2f;
711dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
712dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
7133d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
7143d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
715b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
71632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
71732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
71832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setXProgress(float r) {
71932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
7203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.min(2.0f, r);
7213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.max(-2.0f, r);
72232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
72332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mXProgress = r;
72432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
72532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
72632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
72732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r *= 0.2f;
729dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
730dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
73132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
73232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        void setMode(int mode) {
7343d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = mode;
7353d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
7363d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
7373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForNeutralPosition() {
738839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, 0);
7393d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
7403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
7413d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForOffscreenPosition() {
742839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, 0);
743839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
744839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
745839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForNeutralPosition(float velocity) {
746839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, velocity);
747839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
748839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
749839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForOffscreenPosition(float velocity) {
750839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, velocity);
7513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
7523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
753839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private float getDuration(boolean invert, float velocity) {
7543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mView != null) {
7553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
7563d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
757839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
758839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        Math.pow(viewLp.verticalOffset, 2));
759dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
760dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                        Math.pow(0.4f * mSlideAmount, 2));
761839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
762839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                if (velocity == 0) {
763839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
764839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                } else {
765839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float duration = invert ? d / Math.abs(velocity) :
766839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            (maxd - d) / Math.abs(velocity);
767839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (duration < MINIMUM_ANIMATION_DURATION ||
768839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            duration > DEFAULT_ANIMATION_DURATION) {
769839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return getDuration(invert, 0);
770839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    } else {
771839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return duration;
772839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    }
773839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                }
7743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            }
7753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return 0;
7763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
7773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
778e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
779e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
780839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getYProgress() {
78132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mYProgress;
78232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
78332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
784e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        // Used for animations
785e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy        @SuppressWarnings({"UnusedDeclaration"})
786839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getXProgress() {
78732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mXProgress;
78832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
78944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
79044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
79144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
79244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public void onRemoteAdapterConnected() {
79344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super.onRemoteAdapterConnected();
7941480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // On first run, we want to set the stack to the end.
79596d8d56302da81b24333b204e6d7f15064538036Adam Cohen        if (mWhichChild == -1) {
79696d8d56302da81b24333b204e6d7f15064538036Adam Cohen            mWhichChild = 0;
7973042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen        }
79896d8d56302da81b24333b204e6d7f15064538036Adam Cohen        setDisplayedChild(mWhichChild);
79944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
80032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    LayoutParams createOrReuseLayoutParams(View v) {
8029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
8039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (currentLp instanceof LayoutParams) {
8049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) currentLp;
8059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setHorizontalOffset(0);
8069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setVerticalOffset(0);
807839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
808839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
8099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return lp;
8109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        return new LayoutParams(v);
81232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
81332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    @Override
8159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        boolean dataChanged = mDataChanged;
8179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (dataChanged) {
8189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            handleDataChanged();
8199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // if the data changes, mWhichChild might be out of the bounds of the adapter
8219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // in this case, we reset mWhichChild to the beginning
8229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mWhichChild >= mAdapter.getCount())
8239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mWhichChild = 0;
8249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            showOnly(mWhichChild, true, true);
8266364f2bbe5254b4274f3feffc48f4259eacc205eWinson Chung            refreshChildren();
8279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final int childCount = getChildCount();
8309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        for (int i = 0; i < childCount; i++) {
8319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            final View child = getChildAt(i);
8329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childRight = mPaddingLeft + child.getMeasuredWidth();
8349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childBottom = mPaddingTop + child.getMeasuredHeight();
8359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
8369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
8389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
8399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        mDataChanged = false;
8439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        onLayout();
8449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
8459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
846839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private void measureChildren() {
847839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int count = getChildCount();
848839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
849839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int childHeight = Math.round(mMeasuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR))
850839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                - mPaddingTop - mPaddingBottom;
851839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
852839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        for (int i = 0; i < count; i++) {
853839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            final View child = getChildAt(i);
854839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
855839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
856839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
857839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
858839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
859839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    @Override
860839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
861839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
862839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
863839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
864839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
865839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
866839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
867839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
868839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // We need to deal with the case where our parent hasn't told us how
869839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // big we should be. In this case we should
870839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR);
871839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
872839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ?
873839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    Math.round(mReferenceChildHeight * (1 + factor)) +
874839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingTop + mPaddingBottom : 0;
875839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
876839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ? Math.min(
877839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    Math.round(mReferenceChildHeight * (1 + factor)) + mPaddingTop +
878839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingBottom, heightSpecSize) : 0;
879839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
880839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
881839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
882839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
883839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingRight : 0;
884839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
885839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
886839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingRight, widthSpecSize) : 0;
887839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
888839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
889839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        setMeasuredDimension(widthSpecSize, heightSpecSize);
890839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        measureChildren();
891839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
892839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
8939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    class LayoutParams extends ViewGroup.LayoutParams {
8949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int horizontalOffset;
8959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int verticalOffset;
8969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        View mView;
897d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        int left, top, right, bottom;
898d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect parentRect = new Rect();
899d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect invalidateRect = new Rect();
900d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final RectF invalidateRectf = new RectF();
901d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        private final Rect globalInvalidateRect = new Rect();
9029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(View view) {
9049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(0, 0);
905839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
906839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
9079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
9089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
9099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mView = view;
9109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(Context c, AttributeSet attrs) {
9139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(c, attrs);
9149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
9159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
916839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
917839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
918b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
919b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
9209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void invalidateGlobalRegion(View v, Rect r) {
921d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            // We need to make a new rect here, so as not to modify the one passed
922d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            globalInvalidateRect.set(r);
9239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View p = v;
9249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
9259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            boolean firstPass = true;
9279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            parentRect.set(0, 0, 0, 0);
9289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int depth = 0;
929b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen            while (p.getParent() != null && p.getParent() instanceof View
930d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    && !parentRect.contains(globalInvalidateRect)) {
9319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (!firstPass) {
932d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
933d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                            - p.getScrollY());
9349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    depth++;
9359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                }
9369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                firstPass = false;
9379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                p = (View) p.getParent();
938b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                parentRect.set(p.getScrollX(), p.getScrollY(),
939b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                               p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
940839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
9419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
9429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
943d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top,
944d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                    globalInvalidateRect.right, globalInvalidateRect.bottom);
945d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
946d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen
947d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        Rect getInvalidateRect() {
948d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen            return invalidateRect;
949d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        }
9509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
951d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen        void resetInvalidateRect() {
952d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen           invalidateRect.set(0, 0, 0, 0);
9539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
95432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
955a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase        // This is public so that ObjectAnimator can access it
9569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setVerticalOffset(int newVerticalOffset) {
9579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int offsetDelta = newVerticalOffset - verticalOffset;
9589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = newVerticalOffset;
95932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
9619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
9629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
9639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
9649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
965d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                invalidateRectf.set(mView.getLeft(), top, mView.getRight(), bottom);
9669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
9689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
9699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
9709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
9719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
972d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                invalidateRect.union((int) Math.floor(invalidateRectf.left),
9739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
9749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
9759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
9769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
9789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
9799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setHorizontalOffset(int newHorizontalOffset) {
9829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int offsetDelta = newHorizontalOffset - horizontalOffset;
9839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = newHorizontalOffset;
9849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
9869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
9879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
9889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
9899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.set(left,  mView.getTop(), right, mView.getBottom());
9909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
9929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
9939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
9949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
9959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
9969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
997d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen                invalidateRect.union((int) Math.floor(invalidateRectf.left),
9989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
9999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
10009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
10019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
10039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
10049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
100532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
100632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
10079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static class HolographicHelper {
10089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mHolographicPaint = new Paint();
10099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mErasePaint = new Paint();
1010839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private final Paint mBlurPaint = new Paint();
101132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1012dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        HolographicHelper(Context context) {
1013dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            initializePaints(context);
10149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
1016dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        void initializePaints(Context context) {
1017dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            final float density = context.getResources().getDisplayMetrics().density;
1018dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
10199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setColor(0xff6699ff);
10209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setFilterBitmap(true);
1021839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
10229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
10239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setFilterBitmap(true);
1024dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mBlurPaint.setMaskFilter(new BlurMaskFilter(2*density, BlurMaskFilter.Blur.NORMAL));
10259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        Bitmap createOutline(View v) {
10289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
10299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                return null;
10309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
10319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
10339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    Bitmap.Config.ARGB_8888);
10349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Canvas canvas = new Canvas(bitmap);
103532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
10369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float rotationX = v.getRotationX();
1037839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float rotation = v.getRotation();
1038839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float translationY = v.getTranslationY();
10399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(0);
1040839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(0);
1041839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(0);
10429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.draw(canvas);
10439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(rotationX);
1044839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(rotation);
1045839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(translationY);
10469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            drawOutline(canvas, bitmap);
10489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return bitmap;
10499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
10509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
10519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final Matrix id = new Matrix();
10529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void drawOutline(Canvas dest, Bitmap src) {
1053839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int[] xy = new int[2];
1054839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
1055839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Canvas maskCanvas = new Canvas(mask);
1056839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            maskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
10579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawColor(0, PorterDuff.Mode.CLEAR);
10589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.setMatrix(id);
1059839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
10609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mask.recycle();
10619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
106232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
106344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen}
1064