StackView.java revision 2794eb3b02e2404d453d3ad22a8a85a138130a07
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
19839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.animation.PropertyValuesHolder;
20a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator;
2144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context;
22dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohenimport android.content.res.Resources;
2332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap;
24839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter;
2532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas;
2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix;
2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint;
2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff;
2932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode;
3044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect;
319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF;
32839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter;
3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet;
34dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohenimport android.util.DisplayMetrics;
3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log;
3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent;
3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker;
3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View;
3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration;
4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup;
41839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.view.View.MeasureSpec;
42839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.view.ViewGroup.LayoutParams;
43b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator;
4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView;
4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView
4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/**
4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe
4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children.
5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator {
5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final String TAG = "StackView";
5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Default animation parameters
5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen    private final int DEFAULT_ANIMATION_DURATION = 400;
58b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen    private final int MINIMUM_ANIMATION_DURATION = 50;
5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
61839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Parameters effecting the perspective visuals
62839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
63839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f;
64839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static float PERSPECTIVE_SCALE_FACTOR = 0.35f;
65839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
66839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
67839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Represent the two possible stack modes, one where items slide up, and the other
68839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * where items slide down. The perspective is also inverted between these two modes.
69839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
70839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_UP = 0;
71839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int ITEMS_SLIDE_DOWN = 1;
72839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
73839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
7444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These specify the different gesture states
7544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
765b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_NONE = 0;
775b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_UP = 1;
785b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_DOWN = 2;
7944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
8044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Specifies how far you need to swipe (up or down) before it
8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * will be consider a completed gesture when you lift your finger
8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
845b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SWIPE_THRESHOLD_RATIO = 0.35f;
855b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SLIDE_UP_RATIO = 0.7f;
8644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Sentinel value for no current active pointer.
8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Used by {@link #mActivePointerId}.
9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private static final int INVALID_POINTER = -1;
9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
94839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
95839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen     */
96839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private static final int NUM_ACTIVE_VIEWS = 5;
97839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
98dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private static final int FRAME_PADDING = 4;
99839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
100839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    /**
10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These variables are all related to the current state of touch interaction
10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * with the stack
10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialY;
10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialX;
10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mActivePointerId;
10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mYVelocity = 0;
10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeGestureType = GESTURE_NONE;
109dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mSlideAmount;
11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeThreshold;
11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mTouchSlop;
11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mMaximumVelocity;
11344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private VelocityTracker mVelocityTracker;
11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static HolographicHelper sHolographicHelper;
11632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private ImageView mHighlight;
11732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private StackSlider mStackSlider;
11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private boolean mFirstLayoutHappened = false;
1199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private ViewGroup mAncestorContainingAllChildren = null;
1209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private int mAncestorHeight = 0;
121839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private int mStackMode;
122dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    private int mFramePadding;
12344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context) {
12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context);
12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context, AttributeSet attrs) {
13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context, attrs);
13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void initStackView() {
1351b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen        configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2);
13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        setStaticTransformationsEnabled(true);
13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
138b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        mTouchSlop = configuration.getScaledTouchSlop();
13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
14132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
14232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight = new ImageView(getContext());
14332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
14432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
14532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mStackSlider = new StackSlider();
14632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (sHolographicHelper == null) {
148dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            sHolographicHelper = new HolographicHelper(mContext);
14932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
1509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipChildren(false);
1519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipToPadding(false);
1521480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen
153839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // This sets the form of the StackView, which is currently to have the perspective-shifted
154839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // views above the active view, and have items slide down when sliding out. The opposite is
155839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // available by using ITEMS_SLIDE_UP.
156839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        mStackMode = ITEMS_SLIDE_DOWN;
157839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
1581480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // This is a flag to indicate the the stack is loading for the first time
1591480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        mWhichChild = -1;
160dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
161dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // Adjust the frame padding based on the density, since the highlight changes based
162dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        // on the density
163dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        final float density = mContext.getResources().getDisplayMetrics().density;
164dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
16544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
16644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
16744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
16844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
16944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
17044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void animateViewForTransition(int fromIndex, int toIndex, View view) {
17144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (fromIndex == -1 && toIndex == 0) {
17244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item in
17344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (view.getAlpha() == 1) {
17444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                view.setAlpha(0);
17544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
176b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(VISIBLE);
177b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
1782794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f);
1792794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            fadeIn.setDuration(DEFAULT_ANIMATION_DURATION);
18044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            fadeIn.start();
18144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
18244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item in
18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setVisibility(VISIBLE);
18432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
18544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
186839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
18744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
188b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
1892794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f);
1902794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
1912794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
192839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    slideInX, slideInY);
1932794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
194839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
195839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
19744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item out
19844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
19944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
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();
21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
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
229f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void transformViewAtIndex(int index, View view) {
230dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float maxPerpectiveShift = mMeasuredHeight * PERSPECTIVE_SHIFT_FACTOR;
231839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
232f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        if (index == mNumActiveViews -1) index--;
233839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
234f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        float r = (index * 1.0f) / (mNumActiveViews - 2);
235839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
236f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
2372794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", scale);
2382794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", scale);
239839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
240f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        r = (float) Math.pow(r, 2);
241839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
242f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
243dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float perspectiveTranslation = -stackDirection * r * maxPerpectiveShift;
244dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float scaleShiftCorrection = stackDirection * (1 - scale) *
245dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                (mMeasuredHeight * (1 - PERSPECTIVE_SHIFT_FACTOR) / 2.0f);
246dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        float transY = perspectiveTranslation + scaleShiftCorrection;
247f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen
2482794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY);
2492794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY, translationY);
2502794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase        pa.setDuration(100);
251f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        pa.start();
252f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    }
253f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen
254f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen    private void updateChildTransforms() {
255f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen        for (int i = 0; i < mNumActiveViews - 1; i++) {
256f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            View v = getViewAtRelativeIndex(i);
257f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            if (v != null) {
258f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen                transformViewAtIndex(i, v);
259f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            }
260839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
26144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
26244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
263dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    @Override
264dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    FrameLayout getFrameForChild() {
265dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        FrameLayout fl = new FrameLayout(mContext);
266dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
267dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        return fl;
268dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen    }
269dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
27044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
27144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Apply any necessary tranforms for the child that is being added.
27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
27344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void applyTransformForChildAtIndex(View child, int relativeIndex) {
27444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
27544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
27644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
2779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void dispatchDraw(Canvas canvas) {
2789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        super.dispatchDraw(canvas);
2799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
2809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
2819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // TODO: right now, this code walks up the hierarchy as far as needed and disables clipping
2829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // so that the stack's children can draw outside of the stack's bounds. This is fine within
2839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // the context of widgets in the launcher, but is destructive in general, as the clipping
2849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // values are not being reset. For this to be a full framework level widget, we will need
2859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // framework level support for drawing outside of a parent's bounds.
2869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void disableParentalClipping() {
2879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (mAncestorContainingAllChildren != null) {
2889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            ViewGroup vg = this;
2899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) {
2909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (vg == mAncestorContainingAllChildren) break;
2919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                vg = (ViewGroup) vg.getParent();
2929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                vg.setClipChildren(false);
2939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                vg.setClipToPadding(false);
29444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
29544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
2969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
29744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
2989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void onLayout() {
29944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mFirstLayoutHappened) {
300dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
301f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen            updateChildTransforms();
302dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount);
30344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mFirstLayoutHappened = true;
30444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
30544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
30644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
30744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
30844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onInterceptTouchEvent(MotionEvent ev) {
30944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
31044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch(action & MotionEvent.ACTION_MASK) {
31144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_DOWN: {
31244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (mActivePointerId == INVALID_POINTER) {
31344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialX = ev.getX();
31444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialY = ev.getY();
31544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mActivePointerId = ev.getPointerId(0);
31644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
31744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
31844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
31944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
32044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                int pointerIndex = ev.findPointerIndex(mActivePointerId);
32144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (pointerIndex == INVALID_POINTER) {
32244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    // no data for our primary pointer, this shouldn't happen, log it
32344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    Log.d(TAG, "Error: No data for our primary pointer.");
32444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    return false;
32544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
32644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float newY = ev.getY(pointerIndex);
32744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float deltaY = newY - mInitialY;
32844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
32932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
33044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
33144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
33244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
33344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
33444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
33544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
33644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP:
33744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
33844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
33944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
34044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
34144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
34244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
34344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return mSwipeGestureType != GESTURE_NONE;
34444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
34544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
34632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private void beginGestureIfNeeded(float deltaY) {
34732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
3483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
34932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            cancelLongPress();
35032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            requestDisallowInterceptTouchEvent(true);
35132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
352839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int activeIndex;
353839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
354839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
355839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        mNumActiveViews - 1 : mNumActiveViews - 2;
356839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
357839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ?
358839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        mNumActiveViews - 2 : mNumActiveViews - 1;
359839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
36032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
3613d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mAdapter == null) return;
3623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3631b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen            if (mLoopViews) {
3641b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen                mStackSlider.setMode(StackSlider.NORMAL_MODE);
3651b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == 0) {
3663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE);
3673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) {
3683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                activeIndex--;
3693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                mStackSlider.setMode(StackSlider.END_OF_STACK_MODE);
3703d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else {
3713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                mStackSlider.setMode(StackSlider.NORMAL_MODE);
37232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
3733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            View v = getViewAtRelativeIndex(activeIndex);
3753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (v == null) return;
3763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
378839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHighlight.setRotation(v.getRotation());
379839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHighlight.setTranslationY(v.getTranslationY());
3803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mHighlight.bringToFront();
3813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            v.bringToFront();
3823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mStackSlider.setView(v);
3833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (swipeGestureType == GESTURE_SLIDE_DOWN)
3853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                v.setVisibility(VISIBLE);
3863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // We only register this gesture if we've made it this far without a problem
3883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mSwipeGestureType = swipeGestureType;
38932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
39032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
39132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
39344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onTouchEvent(MotionEvent ev) {
39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
39544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
39644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerIndex == INVALID_POINTER) {
39744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // no data for our primary pointer, this shouldn't happen, log it
39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            Log.d(TAG, "Error: No data for our primary pointer.");
39944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            return false;
40044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
40144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
40244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
40332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float newX = ev.getX(pointerIndex);
40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float deltaY = newY - mInitialY;
40532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float deltaX = newX - mInitialX;
40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker == null) {
40744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = VelocityTracker.obtain();
40844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
40944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mVelocityTracker.addMovement(ev);
41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch (action & MotionEvent.ACTION_MASK) {
41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
41332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
41432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
415dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float rx = deltaX / (mSlideAmount * 1.0f);
41632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
417dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
418839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
41932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(1 - r);
42032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
42132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
42232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
423dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
424839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
42532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(r);
42632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
42732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
42944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
43044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP: {
43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                handlePointerUp(ev);
43344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
43444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
43644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
43744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
43844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
43944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
44044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return true;
44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
44744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
44844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final Rect touchRect = new Rect();
44944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void onSecondaryPointerUp(MotionEvent ev) {
45044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int activePointerIndex = ev.getActionIndex();
45144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int pointerId = ev.getPointerId(activePointerIndex);
45244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerId == mActivePointerId) {
45344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1
45544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    : mNumActiveViews - 2;
45644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
45744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            View v = getViewAtRelativeIndex(activeViewIndex);
45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (v == null) return;
45944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
46044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Our primary pointer has gone up -- let's see if we can find
46144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // another pointer on the view. If so, then we should replace
46244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // our primary pointer with this new pointer and adjust things
46344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // so that the view doesn't jump
46444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            for (int index = 0; index < ev.getPointerCount(); index++) {
46544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (index != activePointerIndex) {
46644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
46744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float x = ev.getX(index);
46844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float y = ev.getY(index);
46944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
47044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
4715b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy                    if (touchRect.contains(Math.round(x), Math.round(y))) {
47244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldX = ev.getX(activePointerIndex);
47344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldY = ev.getY(activePointerIndex);
47444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
47544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // adjust our frame of reference to avoid a jump
47644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialY += (y - oldY);
47744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialX += (x - oldX);
47844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
47944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mActivePointerId = ev.getPointerId(index);
48044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        if (mVelocityTracker != null) {
48144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                            mVelocityTracker.clear();
48244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        }
48344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // ok, we're good, we found a new pointer which is touching the active view
48444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        return;
48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    }
48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
48844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
4893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // so end the gesture
49044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            handlePointerUp(ev);
49144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
49244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
49344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
49444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void handlePointerUp(MotionEvent ev) {
49544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
49644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
49744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int deltaY = (int) (newY - mInitialY);
49844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (mVelocityTracker != null) {
5003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
5013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
5023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
50344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
50444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker != null) {
50544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker.recycle();
50644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = null;
50744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
50844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
5103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
51144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe down
512839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
513839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showNext();
514839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
515839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showPrevious();
516839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
51732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
5183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
5193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
52044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe up
521839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP) {
522839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showPrevious();
523839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
524839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                showNext();
525839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
526839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
52732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
528839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
52944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe up far enough, snap back down
530839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
531839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
532839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
533839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
534839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
535839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
536839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
53732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
538b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
5392794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress);
5402794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
5412794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
542839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
5432794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
544839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.setInterpolator(new LinearInterpolator());
545839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
54632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
54744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe down far enough, snap back up
548839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
549839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int duration;
550839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
551839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForNeutralPosition());
552839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            } else {
553839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
554839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            }
5553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
556b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
5572794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackY =
5582794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase                    PropertyValuesHolder.ofFloat("YProgress",finalYProgress);
5592794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
5602794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
561839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    snapBackX, snapBackY);
5622794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase            pa.setDuration(duration);
563839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            pa.start();
56444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
56644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mSwipeGestureType = GESTURE_NONE;
56832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
56932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
57032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private class StackSlider {
57132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        View mView;
57232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mYProgress;
57332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mXProgress;
57432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
5753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int NORMAL_MODE = 0;
5763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int BEGINNING_OF_STACK_MODE = 1;
5773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int END_OF_STACK_MODE = 2;
5783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
5793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        int mMode = NORMAL_MODE;
5803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
581b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider() {
582b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
583b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
584b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider(StackSlider copy) {
585b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mView = copy.mView;
586b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mYProgress = copy.mYProgress;
587b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mXProgress = copy.mXProgress;
5883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = copy.mMode;
589b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
590b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
59132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float cubic(float r) {
592839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
59332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
59432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
59532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float highlightAlphaInterpolator(float r) {
59632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.4f;
59732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r < pivot) {
598839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(r / pivot);
59932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
600839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
60132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
60232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
60332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
60432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float viewAlphaInterpolator(float r) {
60532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.3f;
60632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r > pivot) {
607839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
60832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
60932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0;
61032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
61132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
61232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
613b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        private float rotationInterpolator(float r) {
614b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            float pivot = 0.2f;
615b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            if (r < pivot) {
616b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return 0;
617b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            } else {
618839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                return (r - pivot) / (1 - pivot);
619b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
620b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
621b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
62232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        void setView(View v) {
62332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mView = v;
62432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
62532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
62632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setYProgress(float r) {
62732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
62832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.min(1.0f, r);
62932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.max(0, r);
63032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
63132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mYProgress = r;
63232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
63332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
63432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
635839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
636839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
6373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            switch (mMode) {
6383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case NORMAL_MODE:
639dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
640dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
6413d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
6423d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
643839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float alpha = viewAlphaInterpolator(1 - r);
6443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
6463d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // so that they don't interfere with click events.
6473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
6483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(VISIBLE);
6493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    } else if (alpha == 0 && mView.getAlpha() != 0
6503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                            && mView.getVisibility() == VISIBLE) {
6513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(INVISIBLE);
6523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    }
653b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
6543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setAlpha(alpha);
655839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
656839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
6573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
6583d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case BEGINNING_OF_STACK_MODE:
659839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = r * 0.2f;
660dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
661dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
6623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
6633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
6643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case END_OF_STACK_MODE:
665839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    r = (1-r) * 0.2f;
666dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
667dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                    highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
6683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
6693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
670b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
67132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
67232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
67332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setXProgress(float r) {
67432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
6753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.min(2.0f, r);
6763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.max(-2.0f, r);
67732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
67832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mXProgress = r;
67932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
68032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
68132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
68232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r *= 0.2f;
684dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
685dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
68632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
68732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        void setMode(int mode) {
6893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = mode;
6903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
6913d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForNeutralPosition() {
693839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, 0);
6943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
6953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForOffscreenPosition() {
697839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, 0);
698839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
699839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
700839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForNeutralPosition(float velocity) {
701839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(false, velocity);
702839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
703839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
704839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float getDurationForOffscreenPosition(float velocity) {
705839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            return getDuration(true, velocity);
7063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
7073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
708839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private float getDuration(boolean invert, float velocity) {
7093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mView != null) {
7103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
7113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
712839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
713839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        Math.pow(viewLp.verticalOffset, 2));
714dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
715dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen                        Math.pow(0.4f * mSlideAmount, 2));
716839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
717839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                if (velocity == 0) {
718839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
719839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                } else {
720839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    float duration = invert ? d / Math.abs(velocity) :
721839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            (maxd - d) / Math.abs(velocity);
722839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    if (duration < MINIMUM_ANIMATION_DURATION ||
723839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                            duration > DEFAULT_ANIMATION_DURATION) {
724839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return getDuration(invert, 0);
725839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    } else {
726839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                        return duration;
727839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    }
728839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                }
7293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            }
7303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return 0;
7313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
7323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
733839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getYProgress() {
73432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mYProgress;
73532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
73632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
737839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        public float getXProgress() {
73832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mXProgress;
73932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
74044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
74144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
74244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
74344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public void onRemoteAdapterConnected() {
74444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super.onRemoteAdapterConnected();
7451480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // On first run, we want to set the stack to the end.
7461480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        if (mAdapter != null && mWhichChild == -1) {
7471480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen            mWhichChild = mAdapter.getCount() - 1;
7481480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        }
7493042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen        if (mWhichChild >= 0) {
7503042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen            setDisplayedChild(mWhichChild);
7513042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen        }
75244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
75332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    LayoutParams createOrReuseLayoutParams(View v) {
7559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
7569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (currentLp instanceof LayoutParams) {
7579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) currentLp;
7589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setHorizontalOffset(0);
7599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setVerticalOffset(0);
760839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
761839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            lp.width = 0;
7629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return lp;
7639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
7649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        return new LayoutParams(v);
76532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
76632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    @Override
7689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        boolean dataChanged = mDataChanged;
7709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (dataChanged) {
7719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            handleDataChanged();
7729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // if the data changes, mWhichChild might be out of the bounds of the adapter
7749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // in this case, we reset mWhichChild to the beginning
7759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mWhichChild >= mAdapter.getCount())
7769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mWhichChild = 0;
7779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            showOnly(mWhichChild, true, true);
7796364f2bbe5254b4274f3feffc48f4259eacc205eWinson Chung            refreshChildren();
7809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
7819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final int childCount = getChildCount();
7839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        for (int i = 0; i < childCount; i++) {
7849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            final View child = getChildAt(i);
7859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childRight = mPaddingLeft + child.getMeasuredWidth();
7879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childBottom = mPaddingTop + child.getMeasuredHeight();
7889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
7899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
7919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
7929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
7949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        mDataChanged = false;
7969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        onLayout();
7979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
7989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
799839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    private void measureChildren() {
800839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int count = getChildCount();
801839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight;
802839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int childHeight = Math.round(mMeasuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR))
803839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                - mPaddingTop - mPaddingBottom;
804839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
805839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        for (int i = 0; i < count; i++) {
806839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            final View child = getChildAt(i);
807839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
808839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
809839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
810839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
811839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
812839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    @Override
813839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
814839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
815839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
816839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
817839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
818839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
819839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
820839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
821839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // We need to deal with the case where our parent hasn't told us how
822839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        // big we should be. In this case we should
823839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR);
824839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
825839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ?
826839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    Math.round(mReferenceChildHeight * (1 + factor)) +
827839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingTop + mPaddingBottom : 0;
828839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
829839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            heightSpecSize = haveChildRefSize ? Math.min(
830839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    Math.round(mReferenceChildHeight * (1 + factor)) + mPaddingTop +
831839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingBottom, heightSpecSize) : 0;
832839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
833839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
834839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
835839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft +
836839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingRight : 0;
837839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
838839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft +
839839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                    mPaddingRight, widthSpecSize) : 0;
840839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        }
841839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
842839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        setMeasuredDimension(widthSpecSize, heightSpecSize);
843839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        measureChildren();
844839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen    }
845839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
8469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    class LayoutParams extends ViewGroup.LayoutParams {
8479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int horizontalOffset;
8489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int verticalOffset;
8499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        View mView;
8509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(View view) {
8529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(0, 0);
853839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
854839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
8559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
8569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
8579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mView = view;
8589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(Context c, AttributeSet attrs) {
8619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(c, attrs);
8629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
8639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
864839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            width = 0;
865839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            height = 0;
866b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
867b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
8689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private Rect parentRect = new Rect();
8699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void invalidateGlobalRegion(View v, Rect r) {
8709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View p = v;
8719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
8729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            boolean firstPass = true;
8749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            parentRect.set(0, 0, 0, 0);
8759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int depth = 0;
876b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen            while (p.getParent() != null && p.getParent() instanceof View
8779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    && !parentRect.contains(r)) {
8789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (!firstPass) {
879b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                    r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY());
8809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    depth++;
8819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                }
8829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                firstPass = false;
8839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                p = (View) p.getParent();
884b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                parentRect.set(p.getScrollX(), p.getScrollY(),
885b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen                               p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
886839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen
887839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                // TODO: we need to stop early here if we've hit the edge of the screen
888839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                // so as to prevent us from walking too high in the hierarchy. A lot of this
889839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen                // code might become a lot more straightforward.
8909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
8919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (depth > mAncestorHeight) {
8939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mAncestorContainingAllChildren = (ViewGroup) p;
8949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mAncestorHeight = depth;
8959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                disableParentalClipping();
8969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
8979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            p.invalidate(r.left, r.top, r.right, r.bottom);
8999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
90032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private Rect invalidateRect = new Rect();
9029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private RectF invalidateRectf = new RectF();
903a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase        // This is public so that ObjectAnimator can access it
9049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setVerticalOffset(int newVerticalOffset) {
9059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int offsetDelta = newVerticalOffset - verticalOffset;
9069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = newVerticalOffset;
90732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
9099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
9109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
9119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
9129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.set(mView.getLeft(),  top, mView.getRight(), bottom);
9149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
9169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
9179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
9189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
9199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
9209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
9219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
9229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
9239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
9249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
9269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
9279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setHorizontalOffset(int newHorizontalOffset) {
9309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int offsetDelta = newHorizontalOffset - horizontalOffset;
9319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = newHorizontalOffset;
9329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
9349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
9359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
9369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
9379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.set(left,  mView.getTop(), right, mView.getBottom());
9389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
9409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
9419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
9429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
9439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
9449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
9469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
9479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
9489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
9499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
9519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
9529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
95332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
95432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static class HolographicHelper {
9569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mHolographicPaint = new Paint();
9579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mErasePaint = new Paint();
958839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen        private final Paint mBlurPaint = new Paint();
95932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
960dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        HolographicHelper(Context context) {
961dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            initializePaints(context);
9629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
964dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen        void initializePaints(Context context) {
965dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            final float density = context.getResources().getDisplayMetrics().density;
966dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen
9679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setColor(0xff6699ff);
9689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setFilterBitmap(true);
969839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
9709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
9719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setFilterBitmap(true);
972dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen            mBlurPaint.setMaskFilter(new BlurMaskFilter(2*density, BlurMaskFilter.Blur.NORMAL));
9739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        Bitmap createOutline(View v) {
9769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
9779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                return null;
9789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
9799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
9819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    Bitmap.Config.ARGB_8888);
9829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Canvas canvas = new Canvas(bitmap);
98332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
9849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float rotationX = v.getRotationX();
985839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float rotation = v.getRotation();
986839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            float translationY = v.getTranslationY();
9879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(0);
988839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(0);
989839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(0);
9909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.draw(canvas);
9919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(rotationX);
992839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setRotation(rotation);
993839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            v.setTranslationY(translationY);
9949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            drawOutline(canvas, bitmap);
9969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return bitmap;
9979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
9989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
9999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final Matrix id = new Matrix();
10009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void drawOutline(Canvas dest, Bitmap src) {
1001839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            int[] xy = new int[2];
1002839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Bitmap mask = src.extractAlpha(mBlurPaint, xy);
1003839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            Canvas maskCanvas = new Canvas(mask);
1004839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            maskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
10059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawColor(0, PorterDuff.Mode.CLEAR);
10069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.setMatrix(id);
1007839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen            dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
10089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mask.recycle();
10099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
101032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
101144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen}
1012