StackView.java revision 1480fddea874a42adb43b4bcdac6704e4c3e110b
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
1944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport java.util.WeakHashMap;
2044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
2144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.animation.PropertyAnimator;
2244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context;
2332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap;
2432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas;
2532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix;
2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint;
2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff;
2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode;
2944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect;
309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF;
3144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet;
3244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log;
3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent;
3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker;
3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View;
3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration;
3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup;
38b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator;
3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView;
4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView
4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/**
4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe
4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children.
4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */
4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator {
4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final String TAG = "StackView";
4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Default animation parameters
5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen    private final int DEFAULT_ANIMATION_DURATION = 400;
53b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen    private final int MINIMUM_ANIMATION_DURATION = 50;
5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These specify the different gesture states
5744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
585b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_NONE = 0;
595b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_UP = 1;
605b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final int GESTURE_SLIDE_DOWN = 2;
6144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
6344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Specifies how far you need to swipe (up or down) before it
6444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * will be consider a completed gesture when you lift your finger
6544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
665b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SWIPE_THRESHOLD_RATIO = 0.35f;
675b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy    private static final float SLIDE_UP_RATIO = 0.7f;
6844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
6944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>();
7044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
7144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
7244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Sentinel value for no current active pointer.
7344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Used by {@link #mActivePointerId}.
7444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
7544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private static final int INVALID_POINTER = -1;
7644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
7744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
7844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * These variables are all related to the current state of touch interaction
7944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * with the stack
8044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialY;
8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private float mInitialX;
8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mActivePointerId;
8444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mYVelocity = 0;
8544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeGestureType = GESTURE_NONE;
8644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mViewHeight;
8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mSwipeThreshold;
8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mTouchSlop;
8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private int mMaximumVelocity;
9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private VelocityTracker mVelocityTracker;
9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static HolographicHelper sHolographicHelper;
9332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private ImageView mHighlight;
9432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private StackSlider mStackSlider;
9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private boolean mFirstLayoutHappened = false;
969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private ViewGroup mAncestorContainingAllChildren = null;
979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private int mAncestorHeight = 0;
9844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
9944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context) {
10044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context);
10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public StackView(Context context, AttributeSet attrs) {
10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super(context, attrs);
10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        initStackView();
10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
10944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void initStackView() {
110b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        configureViewAnimator(4, 2, false);
11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        setStaticTransformationsEnabled(true);
11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
113b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        mTouchSlop = configuration.getScaledTouchSlop();
11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
11544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
11632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
11732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight = new ImageView(getContext());
11832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mHighlight.setLayoutParams(new LayoutParams(mHighlight));
11932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
12032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        mStackSlider = new StackSlider();
12132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
1229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (sHolographicHelper == null) {
1239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            sHolographicHelper = new HolographicHelper();
12432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
1259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipChildren(false);
1269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        setClipToPadding(false);
1271480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen
1281480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // This is a flag to indicate the the stack is loading for the first time
1291480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        mWhichChild = -1;
13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void animateViewForTransition(int fromIndex, int toIndex, View view) {
13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (fromIndex == -1 && toIndex == 0) {
13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item in
13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (view.getAlpha() == 1) {
13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                view.setAlpha(0);
14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
141b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(VISIBLE);
142b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    view, "alpha", view.getAlpha(), 1.0f);
14544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            fadeIn.start();
14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) {
14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item in
14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setVisibility(VISIBLE);
14932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
1513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int largestDuration =
1523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            int duration = largestDuration;
15544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (mYVelocity != 0) {
15644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity);
15744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
15844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
15944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            duration = Math.min(duration, largestDuration);
16044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
16144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
162b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
163b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator slideInY = new PropertyAnimator(duration, animationSlider,
16432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "YProgress", mStackSlider.getYProgress(), 0);
165b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            slideInY.setInterpolator(new LinearInterpolator());
16632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            slideInY.start();
167b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator slideInX = new PropertyAnimator(duration, animationSlider,
16832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "XProgress", mStackSlider.getXProgress(), 0);
169b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            slideInX.setInterpolator(new LinearInterpolator());
17032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            slideInX.start();
17144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) {
17244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Slide item out
17344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
17444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
1753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int largestDuration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
1763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    DEFAULT_ANIMATION_DURATION);
17744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            int duration = largestDuration;
17844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (mYVelocity != 0) {
17944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity);
18044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
18144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
18244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            duration = Math.min(duration, largestDuration);
18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            duration = Math.max(duration, MINIMUM_ANIMATION_DURATION);
18444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
185b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
186b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator slideOutY = new PropertyAnimator(duration, animationSlider,
18732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "YProgress", mStackSlider.getYProgress(), 1);
188b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            slideOutY.setInterpolator(new LinearInterpolator());
18932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            slideOutY.start();
190b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator slideOutX = new PropertyAnimator(duration, animationSlider,
19132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "XProgress", mStackSlider.getXProgress(), 0);
192b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            slideOutX.setInterpolator(new LinearInterpolator());
19332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            slideOutX.start();
19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) {
19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Make sure this view that is "waiting in the wings" is invisible
19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            view.setAlpha(0.0f);
197b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            view.setVisibility(INVISIBLE);
198b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            LayoutParams lp = (LayoutParams) view.getLayoutParams();
199b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            lp.setVerticalOffset(-mViewHeight);
20044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        } else if (toIndex == -1) {
20144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Fade item out
20244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION,
20344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    view, "alpha", view.getAlpha(), 0);
20444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            fadeOut.start();
20544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
20644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
20744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
20844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    /**
20944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     * Apply any necessary tranforms for the child that is being added.
21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen     */
21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    void applyTransformForChildAtIndex(View child, int relativeIndex) {
21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mRotations.containsKey(child)) {
2135b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy            float rotation = (float) (Math.random()*26 - 13);
21444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mRotations.put(child, rotation);
2159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.setRotation(rotation);
21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
21744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
21844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        // Child has been removed
21944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (relativeIndex == -1) {
22044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (mRotations.containsKey(child)) {
22144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mRotations.remove(child);
22244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
22344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
22444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
22544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
22644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
2279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void dispatchDraw(Canvas canvas) {
2289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        super.dispatchDraw(canvas);
2299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
2309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
2319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // TODO: right now, this code walks up the hierarchy as far as needed and disables clipping
2329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // so that the stack's children can draw outside of the stack's bounds. This is fine within
2339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // the context of widgets in the launcher, but is destructive in general, as the clipping
2349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // values are not being reset. For this to be a full framework level widget, we will need
2359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    // framework level support for drawing outside of a parent's bounds.
2369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void disableParentalClipping() {
2379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (mAncestorContainingAllChildren != null) {
2389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Log.v(TAG, "Disabling parental clipping.");
2399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            ViewGroup vg = this;
2409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) {
2419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (vg == mAncestorContainingAllChildren) break;
2429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                vg = (ViewGroup) vg.getParent();
2439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                vg.setClipChildren(false);
2449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                vg.setClipToPadding(false);
24544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
24644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
2479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
24844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
2499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private void onLayout() {
25044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (!mFirstLayoutHappened) {
2515b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy            mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight());
2525b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy            mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight);
25344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mFirstLayoutHappened = true;
25444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
25544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
25644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
25744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
25844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onInterceptTouchEvent(MotionEvent ev) {
25944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
26044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch(action & MotionEvent.ACTION_MASK) {
26144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_DOWN: {
26244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (mActivePointerId == INVALID_POINTER) {
26344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialX = ev.getX();
26444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mInitialY = ev.getY();
26544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    mActivePointerId = ev.getPointerId(0);
26644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
26744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
26844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
27044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                int pointerIndex = ev.findPointerIndex(mActivePointerId);
27144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (pointerIndex == INVALID_POINTER) {
27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    // no data for our primary pointer, this shouldn't happen, log it
27344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    Log.d(TAG, "Error: No data for our primary pointer.");
27444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    return false;
27544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
27644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float newY = ev.getY(pointerIndex);
27744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                float deltaY = newY - mInitialY;
27844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
27932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
28044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
28144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
28244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
28344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
28444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
28544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
28644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP:
28744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
28844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
28944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
29044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
29144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
29244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
29344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return mSwipeGestureType != GESTURE_NONE;
29444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
29544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
29632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private void beginGestureIfNeeded(float deltaY) {
29732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
2983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
29932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            cancelLongPress();
30032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            requestDisallowInterceptTouchEvent(true);
30132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
3023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int activeIndex = swipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1
30332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    : mNumActiveViews - 2;
30432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
3053d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mAdapter == null) return;
3063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mCurrentWindowStartUnbounded + activeIndex == 0) {
3083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE);
3093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) {
3103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                activeIndex--;
3113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                mStackSlider.setMode(StackSlider.END_OF_STACK_MODE);
3123d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            } else {
3133d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                mStackSlider.setMode(StackSlider.NORMAL_MODE);
31432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
3153d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3163d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            View v = getViewAtRelativeIndex(activeIndex);
3173d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (v == null) return;
3183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
3203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mHighlight.bringToFront();
3213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            v.bringToFront();
3223d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mStackSlider.setView(v);
3233d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (swipeGestureType == GESTURE_SLIDE_DOWN)
3253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                v.setVisibility(VISIBLE);
3263d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
3273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // We only register this gesture if we've made it this far without a problem
3283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mSwipeGestureType = swipeGestureType;
32932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
33032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
33132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
33244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
33344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public boolean onTouchEvent(MotionEvent ev) {
33444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int action = ev.getAction();
33544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
33644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerIndex == INVALID_POINTER) {
33744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // no data for our primary pointer, this shouldn't happen, log it
33844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            Log.d(TAG, "Error: No data for our primary pointer.");
33944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            return false;
34044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
34144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
34244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
34332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float newX = ev.getX(pointerIndex);
34444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float deltaY = newY - mInitialY;
34532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float deltaX = newX - mInitialX;
34644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker == null) {
34744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = VelocityTracker.obtain();
34844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
34944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mVelocityTracker.addMovement(ev);
35044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
35144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        switch (action & MotionEvent.ACTION_MASK) {
35244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_MOVE: {
35332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                beginGestureIfNeeded(deltaY);
35432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
3553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                float rx = deltaX/(mViewHeight*1.0f);
35632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
35732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f;
35832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(1 - r);
35932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
36032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
36132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
36232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    float r = -(deltaY + mTouchSlop*1.0f)/mViewHeight*1.0f;
36332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setYProgress(r);
36432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    mStackSlider.setXProgress(rx);
36532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    return true;
36644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
36744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
36844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
36944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_UP: {
37044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                handlePointerUp(ev);
37144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
37244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
37344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_POINTER_UP: {
37444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                onSecondaryPointerUp(ev);
37544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
37644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
37744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            case MotionEvent.ACTION_CANCEL: {
37844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mActivePointerId = INVALID_POINTER;
37944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                mSwipeGestureType = GESTURE_NONE;
38044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                break;
38144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
38244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
38344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        return true;
38444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
38544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
38644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private final Rect touchRect = new Rect();
38744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void onSecondaryPointerUp(MotionEvent ev) {
38844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int activePointerIndex = ev.getActionIndex();
38944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        final int pointerId = ev.getPointerId(activePointerIndex);
39044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (pointerId == mActivePointerId) {
39144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1
39344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    : mNumActiveViews - 2;
39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
39544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            View v = getViewAtRelativeIndex(activeViewIndex);
39644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            if (v == null) return;
39744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Our primary pointer has gone up -- let's see if we can find
39944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // another pointer on the view. If so, then we should replace
40044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // our primary pointer with this new pointer and adjust things
40144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // so that the view doesn't jump
40244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            for (int index = 0; index < ev.getPointerCount(); index++) {
40344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                if (index != activePointerIndex) {
40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
40544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float x = ev.getX(index);
40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    float y = ev.getY(index);
40744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
40844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
4095b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy                    if (touchRect.contains(Math.round(x), Math.round(y))) {
41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldX = ev.getX(activePointerIndex);
41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        float oldY = ev.getY(activePointerIndex);
41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
41344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // adjust our frame of reference to avoid a jump
41444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialY += (y - oldY);
41544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mInitialX += (x - oldX);
41644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
41744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        mActivePointerId = ev.getPointerId(index);
41844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        if (mVelocityTracker != null) {
41944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                            mVelocityTracker.clear();
42044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        }
42144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        // ok, we're good, we found a new pointer which is touching the active view
42244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                        return;
42344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                    }
42444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen                }
42544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            }
42644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // if we made it this far, it means we didn't find a satisfactory new pointer :(,
4273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            // so end the gesture
42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            handlePointerUp(ev);
42944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
43044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    private void handlePointerUp(MotionEvent ev) {
43344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int pointerIndex = ev.findPointerIndex(mActivePointerId);
43444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        float newY = ev.getY(pointerIndex);
43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        int deltaY = (int) (newY - mInitialY);
43644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (mVelocityTracker != null) {
4383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
4393d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
4403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        if (mVelocityTracker != null) {
44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker.recycle();
44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            mVelocityTracker = null;
44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
4473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
4483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
44944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe down
45044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            showNext();
45132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
4523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
4533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Swipe threshold exceeded, swipe up
45544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            showPrevious();
45632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mHighlight.bringToFront();
45732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe up far enough, snap back down
4593d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int duration =
4603d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION);
46132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
462b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
463b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
46432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "YProgress", mStackSlider.getYProgress(), 0);
465b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            snapBackY.setInterpolator(new LinearInterpolator());
46632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            snapBackY.start();
467b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
46832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "XProgress", mStackSlider.getXProgress(), 0);
469b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            snapBackX.setInterpolator(new LinearInterpolator());
47032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            snapBackX.start();
47132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
47244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen            // Didn't swipe down far enough, snap back up
4733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            int duration = Math.round(mStackSlider.getDurationForOffscreenPosition()*
4743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    DEFAULT_ANIMATION_DURATION);
4753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
476b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            StackSlider animationSlider = new StackSlider(mStackSlider);
477b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider,
47832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "YProgress", mStackSlider.getYProgress(), 1);
479b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            snapBackY.setInterpolator(new LinearInterpolator());
48032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            snapBackY.start();
481b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider,
48232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                    "XProgress", mStackSlider.getXProgress(), 0);
483b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            snapBackX.setInterpolator(new LinearInterpolator());
48432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            snapBackX.start();
48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        }
48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mActivePointerId = INVALID_POINTER;
48844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        mSwipeGestureType = GESTURE_NONE;
48932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
49032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
49132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    private class StackSlider {
49232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        View mView;
49332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mYProgress;
49432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float mXProgress;
49532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
4963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int NORMAL_MODE = 0;
4973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int BEGINNING_OF_STACK_MODE = 1;
4983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        static final int END_OF_STACK_MODE = 2;
4993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
5003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        int mMode = NORMAL_MODE;
5013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
502b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider() {
503b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
504b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
505b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        public StackSlider(StackSlider copy) {
506b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mView = copy.mView;
507b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mYProgress = copy.mYProgress;
508b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            mXProgress = copy.mXProgress;
5093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = copy.mMode;
510b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
511b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
51232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float cubic(float r) {
51332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return (float) (Math.pow(2*r-1, 3) + 1)/2.0f;
51432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
51532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
51632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float highlightAlphaInterpolator(float r) {
51732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.4f;
51832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r < pivot) {
51932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0.85f*cubic(r/pivot);
52032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
52132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0.85f*cubic(1 - (r-pivot)/(1-pivot));
52232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
52332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
52432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
52532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        private float viewAlphaInterpolator(float r) {
52632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            float pivot = 0.3f;
52732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            if (r > pivot) {
52832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return (r - pivot)/(1 - pivot);
52932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            } else {
53032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen                return 0;
53132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            }
53232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
53332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
534b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        private float rotationInterpolator(float r) {
535b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            float pivot = 0.2f;
536b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            if (r < pivot) {
537b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return 0;
538b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            } else {
539b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen                return (r-pivot)/(1-pivot);
540b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
541b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
542b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
54332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        void setView(View v) {
54432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mView = v;
54532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
54632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
54732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setYProgress(float r) {
54832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
54932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.min(1.0f, r);
55032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            r = Math.max(0, r);
55132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
55232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mYProgress = r;
55332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
55432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
55532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
5563d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            switch (mMode) {
5573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case NORMAL_MODE:
5583d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
5593d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
5603d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
5613d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
5623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    float alpha = viewAlphaInterpolator(1-r);
5633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
5643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // We make sure that views which can't be seen (have 0 alpha) are also invisible
5653d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    // so that they don't interfere with click events.
5663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
5673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(VISIBLE);
5683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    } else if (alpha == 0 && mView.getAlpha() != 0
5693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                            && mView.getVisibility() == VISIBLE) {
5703d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        mView.setVisibility(INVISIBLE);
5713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    }
572b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
5733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setAlpha(alpha);
5743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mView.setRotationX(90.0f*rotationInterpolator(r));
5753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setRotationX(90.0f*rotationInterpolator(r));
5763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
5773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case BEGINNING_OF_STACK_MODE:
5783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    r = r*0.2f;
5793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    viewLp.setVerticalOffset(Math.round(-r*mViewHeight));
5803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    highlightLp.setVerticalOffset(Math.round(-r*mViewHeight));
5813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
5823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
5833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                case END_OF_STACK_MODE:
5843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    r = (1-r)*0.2f;
5853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    viewLp.setVerticalOffset(Math.round(r*mViewHeight));
5863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    highlightLp.setVerticalOffset(Math.round(r*mViewHeight));
5873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    mHighlight.setAlpha(highlightAlphaInterpolator(r));
5883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                    break;
589b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen            }
59032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
59132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
59232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        public void setXProgress(float r) {
59332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            // enforce r between 0 and 1
5943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.min(2.0f, r);
5953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r = Math.max(-2.0f, r);
59632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
59732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            mXProgress = r;
59832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
59932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
60032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
60132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            r *= 0.2f;
6035b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy            viewLp.setHorizontalOffset(Math.round(r*mViewHeight));
6045b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy            highlightLp.setHorizontalOffset(Math.round(r*mViewHeight));
60532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
60632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        void setMode(int mode) {
6083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            mMode = mode;
6093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
6103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForNeutralPosition() {
6123d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return getDuration(false);
6133d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
6143d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6153d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        float getDurationForOffscreenPosition() {
6163d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return getDuration(mMode == END_OF_STACK_MODE ? false : true);
6173d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
6183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        private float getDuration(boolean invert) {
6203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            if (mView != null) {
6213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
6223d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
6233d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset,2) +
6243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        Math.pow(viewLp.verticalOffset,2));
6253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                float maxd = (float) Math.sqrt(Math.pow(mViewHeight, 2) +
6263d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                        Math.pow(0.4f*mViewHeight, 2));
6273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen                return invert ? (1-d/maxd) : d/maxd;
6283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            }
6293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen            return 0;
6303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen        }
6313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen
63232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float getYProgress() {
63332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mYProgress;
63432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
63532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
63632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        float getXProgress() {
63732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen            return mXProgress;
63832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen        }
63944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
64044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen
64144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    @Override
64244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    public void onRemoteAdapterConnected() {
64344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen        super.onRemoteAdapterConnected();
6441480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        // On first run, we want to set the stack to the end.
6451480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        if (mAdapter != null && mWhichChild == -1) {
6461480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen            mWhichChild = mAdapter.getCount() - 1;
6471480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen        }
648b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        setDisplayedChild(mWhichChild);
64944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen    }
65032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    LayoutParams createOrReuseLayoutParams(View v) {
6529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
6539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (currentLp instanceof LayoutParams) {
6549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) currentLp;
6559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setHorizontalOffset(0);
6569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            lp.setVerticalOffset(0);
6579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return lp;
6589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
6599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        return new LayoutParams(v);
66032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
66132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
6629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    @Override
6639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        boolean dataChanged = mDataChanged;
6659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        if (dataChanged) {
6669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            handleDataChanged();
6679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // if the data changes, mWhichChild might be out of the bounds of the adapter
6699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            // in this case, we reset mWhichChild to the beginning
6709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mWhichChild >= mAdapter.getCount())
6719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mWhichChild = 0;
6729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            showOnly(mWhichChild, true, true);
6749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
6759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final int childCount = getChildCount();
6779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        for (int i = 0; i < childCount; i++) {
6789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            final View child = getChildAt(i);
6799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childRight = mPaddingLeft + child.getMeasuredWidth();
6819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int childBottom = mPaddingTop + child.getMeasuredHeight();
6829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            LayoutParams lp = (LayoutParams) child.getLayoutParams();
6839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
6859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
6869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            //TODO: temp until fix in View
6889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.setPivotX(child.getMeasuredWidth()/2);
6899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            child.setPivotY(child.getMeasuredHeight()/2);
6909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
6919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        mDataChanged = false;
6939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        onLayout();
6949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    }
6959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
6969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    class LayoutParams extends ViewGroup.LayoutParams {
6979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int horizontalOffset;
6989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        int verticalOffset;
6999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        View mView;
7009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(View view) {
7029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(0, 0);
7039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
7049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
7059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mView = view;
7069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
7079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        LayoutParams(Context c, AttributeSet attrs) {
7099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            super(c, attrs);
7109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = 0;
7119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = 0;
712b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen        }
713b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen
7149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private Rect parentRect = new Rect();
7159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void invalidateGlobalRegion(View v, Rect r) {
7169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View p = v;
7179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (!(v.getParent() != null && v.getParent() instanceof View)) return;
7189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            View gp = (View) v.getParent();
7209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            boolean firstPass = true;
7219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            parentRect.set(0, 0, 0, 0);
7229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int depth = 0;
7239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            while (gp.getParent() != null && gp.getParent() instanceof View
7249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    && !parentRect.contains(r)) {
7259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                if (!firstPass) {
7269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    r.offset(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY());
7279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    depth++;
7289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                }
7299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                firstPass = false;
7309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                p = (View) p.getParent();
7319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                gp = (View) p.getParent();
7329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                parentRect.set(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY(),
7339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        p.getRight() - gp.getScrollX(), p.getBottom() - gp.getScrollY());
7349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
7359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (depth > mAncestorHeight) {
7379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mAncestorContainingAllChildren = (ViewGroup) p;
7389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mAncestorHeight = depth;
7399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                disableParentalClipping();
7409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
7419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            p.invalidate(r.left, r.top, r.right, r.bottom);
7439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
74432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private Rect invalidateRect = new Rect();
7469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private RectF invalidateRectf = new RectF();
7479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        // This is public so that PropertyAnimator can access it
7489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setVerticalOffset(int newVerticalOffset) {
7499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int offsetDelta = newVerticalOffset - verticalOffset;
7509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            verticalOffset = newVerticalOffset;
75132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
7539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
7549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int top = Math.min(mView.getTop() + offsetDelta, mView.getTop());
7559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom());
7569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.set(mView.getLeft(),  top, mView.getRight(), bottom);
7589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
7609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
7619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
7629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
7639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
7649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
7659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
7669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
7679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
7689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
7709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
7719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
7729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        public void setHorizontalOffset(int newHorizontalOffset) {
7749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            int offsetDelta = newHorizontalOffset - horizontalOffset;
7759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            horizontalOffset = newHorizontalOffset;
7769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (mView != null) {
7789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.requestLayout();
7799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft());
7809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                int right = Math.max(mView.getRight() + offsetDelta, mView.getRight());
7819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.set(left,  mView.getTop(), right, mView.getBottom());
7829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float xoffset = -invalidateRectf.left;
7849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                float yoffset = -invalidateRectf.top;
7859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(xoffset, yoffset);
7869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                mView.getMatrix().mapRect(invalidateRectf);
7879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRectf.offset(-xoffset, -yoffset);
7889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateRect.set((int) Math.floor(invalidateRectf.left),
7909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.floor(invalidateRectf.top),
7919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.right),
7929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                        (int) Math.ceil(invalidateRectf.bottom));
7939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
7949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                invalidateGlobalRegion(mView, invalidateRect);
7959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
7969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
79732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
79832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
7999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen    private static class HolographicHelper {
8009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mHolographicPaint = new Paint();
8019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final Paint mErasePaint = new Paint();
8029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        private final float STROKE_WIDTH = 3.0f;
80332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        HolographicHelper() {
8059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            initializePaints();
8069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void initializePaints() {
8099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setColor(0xff6699ff);
8109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mHolographicPaint.setFilterBitmap(true);
8119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
8129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mErasePaint.setFilterBitmap(true);
8139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        Bitmap createOutline(View v) {
8169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
8179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                return null;
8189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            }
8199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
8219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen                    Bitmap.Config.ARGB_8888);
8229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Canvas canvas = new Canvas(bitmap);
82332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float rotationX = v.getRotationX();
8259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(0);
8269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            canvas.concat(v.getMatrix());
8279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.draw(canvas);
82832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen
8299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            v.setRotationX(rotationX);
8309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            drawOutline(canvas, bitmap);
8329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            return bitmap;
8339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
8349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final Matrix id = new Matrix();
8369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        final Matrix scaleMatrix = new Matrix();
8379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        void drawOutline(Canvas dest, Bitmap src) {
8389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            Bitmap mask = src.extractAlpha();
8399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawColor(0, PorterDuff.Mode.CLEAR);
8419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float xScale = STROKE_WIDTH*2/(dest.getWidth());
8439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            float yScale = STROKE_WIDTH*2/(dest.getHeight());
8449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen
8459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            scaleMatrix.reset();
8469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            scaleMatrix.preScale(1+xScale, 1+yScale, dest.getWidth()/2, dest.getHeight()/2);
8479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.setMatrix(id);
8489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawBitmap(mask, scaleMatrix, mHolographicPaint);
8499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            dest.drawBitmap(mask, id, mErasePaint);
8509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen            mask.recycle();
8519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen        }
85232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen    }
85344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen}
854