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