StackView.java revision 53838d265e36fb50341af168f5a91c3341c3cfc3
1c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen/* Copyright (C) 2010 The Android Open Source Project 244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * 344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Licensed under the Apache License, Version 2.0 (the "License"); 444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * you may not use this file except in compliance with the License. 544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * You may obtain a copy of the License at 644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * 744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * http://www.apache.org/licenses/LICENSE-2.0 844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * 944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Unless required by applicable law or agreed to in writing, software 1044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * distributed under the License is distributed on an "AS IS" BASIS, 1144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * See the License for the specific language governing permissions and 1344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * limitations under the License. 1444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 1544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpackage android.widget; 1744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 18c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohenimport java.lang.ref.WeakReference; 19c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen 20a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator; 21e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guyimport android.animation.PropertyValuesHolder; 2244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context; 2332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap; 24839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter; 2532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas; 2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix; 2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint; 2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff; 2932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode; 3044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect; 319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF; 32d51bbb5b56446519db88f49f2682da782b0069acAdam Cohenimport android.graphics.Region; 33839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter; 3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet; 3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log; 3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent; 3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker; 3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View; 3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration; 4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup; 41b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator; 4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView; 4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView 4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/** 4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe 4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children. 4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator { 5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final String TAG = "StackView"; 5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Default animation parameters 5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 55e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy private static final int DEFAULT_ANIMATION_DURATION = 400; 56026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen private static final int FADE_IN_ANIMATION_DURATION = 800; 57e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy private static final int MINIMUM_ANIMATION_DURATION = 50; 58c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen private static final int STACK_RELAYOUT_DURATION = 100; 5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 61839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Parameters effecting the perspective visuals 62839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 63026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen private static final float PERSPECTIVE_SHIFT_FACTOR_Y = 0.1f; 64026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen private static final float PERSPECTIVE_SHIFT_FACTOR_X = 0.1f; 65026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 6636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mPerspectiveShiftX; 6736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mPerspectiveShiftY; 6836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mNewPerspectiveShiftX; 6936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mNewPerspectiveShiftY; 7036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 71e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"FieldCanBeLocal"}) 7278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen private static final float PERSPECTIVE_SCALE_FACTOR = 0f; 73839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 74839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 75839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Represent the two possible stack modes, one where items slide up, and the other 76839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * where items slide down. The perspective is also inverted between these two modes. 77839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 78839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_UP = 0; 79839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_DOWN = 1; 80839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 81839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These specify the different gesture states 8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 845b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_NONE = 0; 855b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_UP = 1; 865b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_DOWN = 2; 8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Specifies how far you need to swipe (up or down) before it 9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * will be consider a completed gesture when you lift your finger 9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 92a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen private static final float SWIPE_THRESHOLD_RATIO = 0.2f; 93a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen 94a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen /** 95a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen * Specifies the total distance, relative to the size of the stack, 96a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen * that views will be slid, either up or down 97a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen */ 985b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SLIDE_UP_RATIO = 0.7f; 9944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Sentinel value for no current active pointer. 10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Used by {@link #mActivePointerId}. 10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private static final int INVALID_POINTER = -1; 10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 107839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Number of active views in the stack. One fewer view is actually visible, as one is hidden. 108839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 109839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int NUM_ACTIVE_VIEWS = 5; 110839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 111dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private static final int FRAME_PADDING = 4; 112839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 113e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Rect mTouchRect = new Rect(); 114e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy 11526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000; 11626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen 117839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These variables are all related to the current state of touch interaction 11944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * with the stack 12044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 12144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialY; 12244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialX; 12344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mActivePointerId; 12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mYVelocity = 0; 12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeGestureType = GESTURE_NONE; 126dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mSlideAmount; 12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeThreshold; 12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mTouchSlop; 12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mMaximumVelocity; 13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private VelocityTracker mVelocityTracker; 1313352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen private boolean mTransitionIsSetup = false; 13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static HolographicHelper sHolographicHelper; 13432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private ImageView mHighlight; 1358baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private ImageView mClickFeedback; 1368baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private boolean mClickFeedbackIsValid = false; 13732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private StackSlider mStackSlider; 13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mFirstLayoutHappened = false; 13926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen private long mLastInteractionTime = 0; 140839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private int mStackMode; 141dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mFramePadding; 1420ac116b688380489c3690f6f65b282990c221f17Adam Cohen private final Rect stackInvalidateRect = new Rect(); 14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context) { 14544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context); 14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 14944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context, AttributeSet attrs) { 15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context, attrs); 15144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 15244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void initStackView() { 15596d8d56302da81b24333b204e6d7f15064538036Adam Cohen configureViewAnimator(NUM_ACTIVE_VIEWS, 1); 15644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setStaticTransformationsEnabled(true); 15744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 158b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mTouchSlop = configuration.getScaledTouchSlop(); 15944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 16044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 16132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 16232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight = new ImageView(getContext()); 16332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.setLayoutParams(new LayoutParams(mHighlight)); 16432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight)); 1658baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 1668baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback = new ImageView(getContext()); 1678baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback)); 1688baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback)); 1698baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(INVISIBLE); 1708baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 17132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider = new StackSlider(); 17232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (sHolographicHelper == null) { 174dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen sHolographicHelper = new HolographicHelper(mContext); 17532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 1769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipChildren(false); 1779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipToPadding(false); 1781480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen 179839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // This sets the form of the StackView, which is currently to have the perspective-shifted 180839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // views above the active view, and have items slide down when sliding out. The opposite is 181839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // available by using ITEMS_SLIDE_UP. 182839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mStackMode = ITEMS_SLIDE_DOWN; 183839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1841480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // This is a flag to indicate the the stack is loading for the first time 1851480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen mWhichChild = -1; 186dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 187dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // Adjust the frame padding based on the density, since the highlight changes based 188dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // on the density 189dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen final float density = mContext.getResources().getDisplayMetrics().density; 190dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mFramePadding = (int) Math.ceil(density * FRAME_PADDING); 19144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 19244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Animate the views between different relative indexes within the {@link AdapterViewAnimator} 19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 19678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen void transformViewForTransition(int fromIndex, int toIndex, final View view, boolean animate) { 19769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen ObjectAnimator alphaOa = null; 19869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen ObjectAnimator oldAlphaOa = null; 19969d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen 20078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (!animate) { 20178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelSliderAnimator(); 20278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen view.setRotationX(0f); 20378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 20478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setVerticalOffset(0); 20578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setHorizontalOffset(0); 20669d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen } 20769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen 208502045849f760f018f662d752020aef32d4cecd9Adam Cohen if (fromIndex == -1 && toIndex == getNumActiveViews() -1) { 20944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item in 21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (view.getAlpha() == 1) { 21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0); 21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 213502045849f760f018f662d752020aef32d4cecd9Adam Cohen transformViewAtIndex(toIndex, view, false); 214b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(VISIBLE); 215b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 21678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelAlphaAnimator(); 21778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (animate) { 21878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f); 21978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen alphaOa.setDuration(FADE_IN_ANIMATION_DURATION); 22078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setAlphaAnimator(alphaOa); 22178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen alphaOa.start(); 22278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else { 22378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen view.setAlpha(1.0f); 22478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 22596d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (fromIndex == 0 && toIndex == 1) { 22644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item in 22778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelSliderAnimator(); 22844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setVisibility(VISIBLE); 22932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 230839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity)); 231b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 23269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen animationSlider.setView(view); 23378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 23478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (animate) { 23578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f); 23678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 23778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator slideIn = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 23878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideInX, slideInY); 23978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideIn.setDuration(duration); 24078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideIn.setInterpolator(new LinearInterpolator()); 24178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setSliderAnimator(slideIn); 24278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideIn.start(); 24378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else { 24478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setYProgress(0f); 24578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setXProgress(0f); 24678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 24796d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (fromIndex == 1 && toIndex == 0) { 24844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item out 24978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelSliderAnimator(); 250839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity)); 25144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 252b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 25369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen animationSlider.setView(view); 25478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (animate) { 25578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f); 25678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 25778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator slideOut = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 25878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOutX, slideOutY); 25978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOut.setDuration(duration); 26078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOut.setInterpolator(new LinearInterpolator()); 26178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setSliderAnimator(slideOut); 26278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOut.start(); 26378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else { 26478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setYProgress(1.0f); 26578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setXProgress(0f); 26678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 26769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen } else if (toIndex == 0) { 26844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Make sure this view that is "waiting in the wings" is invisible 26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0.0f); 270b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(INVISIBLE); 27178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else if ((fromIndex == 0 || fromIndex == 1) && toIndex > 1) { 27269d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen view.setVisibility(VISIBLE); 27369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen view.setAlpha(1.0f); 27478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen view.setRotationX(0f); 27578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 27678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setVerticalOffset(0); 27778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setHorizontalOffset(0); 278c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen } else if (fromIndex == -1) { 279c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen view.setAlpha(1.0f); 280c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen view.setVisibility(VISIBLE); 28144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (toIndex == -1) { 28244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item out 28378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelAlphaAnimator(); 28478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (animate) { 28578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f); 28678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen alphaOa.setDuration(STACK_RELAYOUT_DURATION); 28778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setAlphaAnimator(alphaOa); 28878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen alphaOa.start(); 28978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else { 29078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen view.setAlpha(0f); 29178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 29244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 293839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 294839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // Implement the faked perspective 295839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (toIndex != -1) { 29678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen transformViewAtIndex(toIndex, view, animate); 297f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 298f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 299839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 30036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private void transformViewAtIndex(int index, final View view, boolean animate) { 30136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float maxPerspectiveShiftY = mPerspectiveShiftY; 30236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float maxPerspectiveShiftX = mPerspectiveShiftX; 303026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 304c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) { 305c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen index = mMaxNumActiveViews - index - 1; 306c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (index == mMaxNumActiveViews - 1) index--; 307c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } else { 308c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen index--; 309c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (index < 0) index++; 310c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } 311026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 312026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float r = (index * 1.0f) / (mMaxNumActiveViews - 2); 313026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 31436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r); 315026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 316c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen float perspectiveTranslationY = r * maxPerspectiveShiftY; 317c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen float scaleShiftCorrectionY = (scale - 1) * 318026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR_Y) / 2.0f); 31936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float transY = perspectiveTranslationY + scaleShiftCorrectionY; 320026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 321026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float perspectiveTranslationX = (1 - r) * maxPerspectiveShiftX; 322026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float scaleShiftCorrectionX = (1 - scale) * 323026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen (getMeasuredWidth() * (1 - PERSPECTIVE_SHIFT_FACTOR_X) / 2.0f); 32436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float transX = perspectiveTranslationX + scaleShiftCorrectionX; 32536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 326c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen // If this view is currently being animated for a certain position, we need to cancel 32769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen // this animation so as not to interfere with the new transformation. 32878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (view instanceof StackFrame) { 32978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelTransformAnimator(); 33069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen } 33169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen 33236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (animate) { 33336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", transX); 33436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY); 33536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder scalePropX = PropertyValuesHolder.ofFloat("scaleX", scale); 33636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder scalePropY = PropertyValuesHolder.ofFloat("scaleY", scale); 33736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 33836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(view, scalePropX, scalePropY, 33936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen translationY, translationX); 340c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen oa.setDuration(STACK_RELAYOUT_DURATION); 34178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (view instanceof StackFrame) { 34278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setTransformAnimator(oa); 34378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 34436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen oa.start(); 34536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } else { 34636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setTranslationX(transX); 34736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setTranslationY(transY); 34836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setScaleX(scale); 34936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setScaleY(scale); 35036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 351026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen } 352026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 3533352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen private void setupStackSlider(View v, int mode) { 3543352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setMode(mode); 3553352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 3563352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setImageBitmap(sHolographicHelper.createOutline(v)); 3573352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setRotation(v.getRotation()); 3583352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setTranslationY(v.getTranslationY()); 359026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen mHighlight.setTranslationX(v.getTranslationX()); 3603352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.bringToFront(); 3613352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen v.bringToFront(); 3623352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setView(v); 3633352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 3643352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen v.setVisibility(VISIBLE); 3653352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 3663352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 3673352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 3680e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 3690e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 3700e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 3713352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @Override 3723352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @android.view.RemotableViewMethod 3733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen public void showNext() { 3740ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (mSwipeGestureType != GESTURE_NONE) return; 3753352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (!mTransitionIsSetup) { 3763352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen View v = getViewAtRelativeIndex(1); 3773352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 3783352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, StackSlider.NORMAL_MODE); 3793352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setYProgress(0); 3803352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setXProgress(0); 3813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 3823352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 3833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen super.showNext(); 3843352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 3853352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 3860e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 3870e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 3880e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 3893352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @Override 3903352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @android.view.RemotableViewMethod 3913352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen public void showPrevious() { 3920ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (mSwipeGestureType != GESTURE_NONE) return; 3933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (!mTransitionIsSetup) { 3943352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen View v = getViewAtRelativeIndex(0); 3953352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 3963352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, StackSlider.NORMAL_MODE); 3973352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setYProgress(1); 3983352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setXProgress(0); 3993352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4003352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4013352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen super.showPrevious(); 4023352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4033352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 40496d8d56302da81b24333b204e6d7f15064538036Adam Cohen @Override 405ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen void showOnly(int childIndex, boolean animate) { 406ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen super.showOnly(childIndex, animate); 40796d8d56302da81b24333b204e6d7f15064538036Adam Cohen 40896d8d56302da81b24333b204e6d7f15064538036Adam Cohen // Here we need to make sure that the z-order of the children is correct 4093352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) { 41096d8d56302da81b24333b204e6d7f15064538036Adam Cohen int index = modulo(i, getWindowSize()); 4110ac116b688380489c3690f6f65b282990c221f17Adam Cohen ViewAndIndex vi = mViewsMap.get(index); 4120ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (vi != null) { 4130ac116b688380489c3690f6f65b282990c221f17Adam Cohen View v = mViewsMap.get(index).view; 4140ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (v != null) v.bringToFront(); 4150ac116b688380489c3690f6f65b282990c221f17Adam Cohen } 41696d8d56302da81b24333b204e6d7f15064538036Adam Cohen } 41778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (mHighlight != null) { 41878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen mHighlight.bringToFront(); 41978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 4203352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mTransitionIsSetup = false; 4218baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedbackIsValid = false; 4228baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4238baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 4248baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void updateClickFeedback() { 4258baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (!mClickFeedbackIsValid) { 4269c295482dd739e80dd49ea0dd3102ad6be6742ddAdam Cohen View v = getViewAtRelativeIndex(1); 4278baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (v != null) { 4288baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v, 4298baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen HolographicHelper.CLICK_FEEDBACK)); 4308baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setTranslationX(v.getTranslationX()); 4318baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setTranslationY(v.getTranslationY()); 4328baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4338baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedbackIsValid = true; 4348baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4358baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4368baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 4378baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen @Override 4388baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void showTapFeedback(View v) { 4398baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen updateClickFeedback(); 4408baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(VISIBLE); 4418baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.bringToFront(); 4428baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen invalidate(); 4438baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 4458baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen @Override 4468baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void hideTapFeedback(View v) { 4478baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(INVISIBLE); 4488baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen invalidate(); 44996d8d56302da81b24333b204e6d7f15064538036Adam Cohen } 45096d8d56302da81b24333b204e6d7f15064538036Adam Cohen 451f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen private void updateChildTransforms() { 45296d8d56302da81b24333b204e6d7f15064538036Adam Cohen for (int i = 0; i < getNumActiveViews(); i++) { 453f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen View v = getViewAtRelativeIndex(i); 454f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen if (v != null) { 45536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen transformViewAtIndex(i, v, false); 456f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 457839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 45944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 46078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen private static class StackFrame extends FrameLayout { 46178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen WeakReference<ObjectAnimator> alphaAnimator; 46278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen WeakReference<ObjectAnimator> transformAnimator; 46378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen WeakReference<ObjectAnimator> sliderAnimator; 46478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 46578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen public StackFrame(Context context) { 46678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen super(context); 46778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 46878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 46978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen void setAlphaAnimator(ObjectAnimator oa) { 47078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen alphaAnimator = new WeakReference<ObjectAnimator>(oa); 47178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 47278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 47378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen void setTransformAnimator(ObjectAnimator oa) { 47478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen transformAnimator = new WeakReference<ObjectAnimator>(oa); 47578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 47678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 47778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen void setSliderAnimator(ObjectAnimator oa) { 47878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen sliderAnimator = new WeakReference<ObjectAnimator>(oa); 47978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 48078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 48178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen boolean cancelAlphaAnimator() { 48278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (alphaAnimator != null) { 48378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator oa = alphaAnimator.get(); 48478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (oa != null) { 48578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen oa.cancel(); 48678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return true; 48778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 48878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 48978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return false; 49078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 49178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 49278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen boolean cancelTransformAnimator() { 49378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (transformAnimator != null) { 49478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator oa = transformAnimator.get(); 49578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (oa != null) { 49678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen oa.cancel(); 49778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return true; 49878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 49978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 50078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return false; 50178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 50278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 50378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen boolean cancelSliderAnimator() { 50478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (sliderAnimator != null) { 50578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator oa = sliderAnimator.get(); 50678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (oa != null) { 50778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen oa.cancel(); 50878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return true; 50978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return false; 51278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 515dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen @Override 516dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen FrameLayout getFrameForChild() { 51778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen StackFrame fl = new StackFrame(mContext); 518dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding); 519dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen return fl; 520dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen } 521dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 52244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 52344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Apply any necessary tranforms for the child that is being added. 52444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 52544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void applyTransformForChildAtIndex(View child, int relativeIndex) { 52644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 52744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 52844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 5299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void dispatchDraw(Canvas canvas) { 5300ac116b688380489c3690f6f65b282990c221f17Adam Cohen canvas.getClipBounds(stackInvalidateRect); 531d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen final int childCount = getChildCount(); 532d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen for (int i = 0; i < childCount; i++) { 533e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen final View child = getChildAt(i); 534e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen LayoutParams lp = (LayoutParams) child.getLayoutParams(); 535e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen if ((lp.horizontalOffset == 0 && lp.verticalOffset == 0) || 536e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen child.getAlpha() == 0f || child.getVisibility() != VISIBLE) { 537e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen lp.resetInvalidateRect(); 538e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen } 5390ac116b688380489c3690f6f65b282990c221f17Adam Cohen stackInvalidateRect.union(lp.getInvalidateRect()); 54044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 541d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen canvas.save(Canvas.CLIP_SAVE_FLAG); 5420ac116b688380489c3690f6f65b282990c221f17Adam Cohen canvas.clipRect(stackInvalidateRect, Region.Op.UNION); 543d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen super.dispatchDraw(canvas); 544d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen canvas.restore(); 5459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 54644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private void onLayout() { 54844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mFirstLayoutHappened) { 549dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight()); 550dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount); 55144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mFirstLayoutHappened = true; 55253838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen updateChildTransforms(); 55344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 55436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 55536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (Float.compare(mPerspectiveShiftY, mNewPerspectiveShiftY) != 0 || 55636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen Float.compare(mPerspectiveShiftX, mNewPerspectiveShiftX) != 0) { 55778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 55836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mPerspectiveShiftY = mNewPerspectiveShiftY; 55936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mPerspectiveShiftX = mNewPerspectiveShiftX; 56053838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen updateChildTransforms(); 56136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 56244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 56344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5640e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 5650e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 5660e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 56844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onInterceptTouchEvent(MotionEvent ev) { 56944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 57044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch(action & MotionEvent.ACTION_MASK) { 57144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_DOWN: { 57244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mActivePointerId == INVALID_POINTER) { 57344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX = ev.getX(); 57444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY = ev.getY(); 57544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(0); 57644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 57744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 57844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 57944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 58044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 58144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 58244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 58344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 58444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 58544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 58644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 58744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 58844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 58932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 59044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 59144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 59244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 59344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 59444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 59544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 59644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: 59744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 59844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 59944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 60044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 60144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 60244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 60344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return mSwipeGestureType != GESTURE_NONE; 60444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 60544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 60632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private void beginGestureIfNeeded(float deltaY) { 60732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 608c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen final int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 60932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen cancelLongPress(); 61032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen requestDisallowInterceptTouchEvent(true); 61132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 612d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen if (mAdapter == null) return; 613ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen final int adapterCount = getCount(); 614d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen 615839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int activeIndex; 616839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 61796d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1; 618839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 61996d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0; 620839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 62132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 622c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen boolean endOfStack = mLoopViews && adapterCount == 1 && 623c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen ((mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_UP) || 624c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen (mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_DOWN)); 625c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen boolean beginningOfStack = mLoopViews && adapterCount == 1 && 626c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen ((mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_UP) || 627c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen (mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_DOWN)); 628c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen 6293352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen int stackMode; 630c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (mLoopViews && !beginningOfStack && !endOfStack) { 6313352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.NORMAL_MODE; 632c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == -1 || beginningOfStack) { 63396d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex++; 6343352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.BEGINNING_OF_STACK_MODE; 635c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == adapterCount - 1 || endOfStack) { 6363352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.END_OF_STACK_MODE; 6373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else { 6383352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.NORMAL_MODE; 63932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 6403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6413352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE; 6423352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 6433d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen View v = getViewAtRelativeIndex(activeIndex); 6443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (v == null) return; 6453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6463352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, stackMode); 6473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We only register this gesture if we've made it this far without a problem 6493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mSwipeGestureType = swipeGestureType; 650a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen cancelHandleClick(); 65132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 65232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 65332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6540e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 6550e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 6560e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 65744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 65844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onTouchEvent(MotionEvent ev) { 659a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen super.onTouchEvent(ev); 660a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen 66144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 66244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 66344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 66444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 66544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 66644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 66744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 66844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 66944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 67032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float newX = ev.getX(pointerIndex); 67144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 67232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float deltaX = newX - mInitialX; 67344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker == null) { 67444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = VelocityTracker.obtain(); 67544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 67644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.addMovement(ev); 67744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 67844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch (action & MotionEvent.ACTION_MASK) { 67944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 68032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 68132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 682dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float rx = deltaX / (mSlideAmount * 1.0f); 68332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 684dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 685839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 68632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(1 - r); 68732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 68832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 68932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 690dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 691839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 69232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(r); 69332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 69432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 69544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 69644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 69744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 69844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: { 69944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 70044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 70144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 70244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 70344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 70444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 70544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 70644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 70744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 70844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 70944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 71044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 71144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 71244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 71344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 71444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 71544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void onSecondaryPointerUp(MotionEvent ev) { 71644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int activePointerIndex = ev.getActionIndex(); 71744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int pointerId = ev.getPointerId(activePointerIndex); 71844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerId == mActivePointerId) { 71944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 72096d8d56302da81b24333b204e6d7f15064538036Adam Cohen int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1; 72144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 72244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(activeViewIndex); 72344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v == null) return; 72444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 72544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Our primary pointer has gone up -- let's see if we can find 72644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // another pointer on the view. If so, then we should replace 72744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // our primary pointer with this new pointer and adjust things 72844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so that the view doesn't jump 72944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (int index = 0; index < ev.getPointerCount(); index++) { 73044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (index != activePointerIndex) { 73144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 73244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float x = ev.getX(index); 73344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float y = ev.getY(index); 73444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 735e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 736e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy if (mTouchRect.contains(Math.round(x), Math.round(y))) { 73744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldX = ev.getX(activePointerIndex); 73844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldY = ev.getY(activePointerIndex); 73944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 74044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // adjust our frame of reference to avoid a jump 74144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY += (y - oldY); 74244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX += (x - oldX); 74344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 74444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(index); 74544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 74644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.clear(); 74744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 74844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ok, we're good, we found a new pointer which is touching the active view 74944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return; 75044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if we made it this far, it means we didn't find a satisfactory new pointer :(, 7543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so end the gesture 75544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 75644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 75944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void handlePointerUp(MotionEvent ev) { 76044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 76144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 76244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int deltaY = (int) (newY - mInitialY); 76326e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen mLastInteractionTime = System.currentTimeMillis(); 76444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 7653d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mVelocityTracker != null) { 7663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 7673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); 7683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 76944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 77044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 77144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.recycle(); 77244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = null; 77344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 77444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 7753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN 7763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 7770ac116b688380489c3690f6f65b282990c221f17Adam Cohen // We reset the gesture variable, because otherwise we will ignore showPrevious() / 7780ac116b688380489c3690f6f65b282990c221f17Adam Cohen // showNext(); 7790ac116b688380489c3690f6f65b282990c221f17Adam Cohen mSwipeGestureType = GESTURE_NONE; 7800ac116b688380489c3690f6f65b282990c221f17Adam Cohen 78144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe down 782839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 783839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showPrevious(); 78496d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else { 78596d8d56302da81b24333b204e6d7f15064538036Adam Cohen showNext(); 786839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 78732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 7883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP 7893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 7900ac116b688380489c3690f6f65b282990c221f17Adam Cohen // We reset the gesture variable, because otherwise we will ignore showPrevious() / 7910ac116b688380489c3690f6f65b282990c221f17Adam Cohen // showNext(); 7920ac116b688380489c3690f6f65b282990c221f17Adam Cohen mSwipeGestureType = GESTURE_NONE; 7930ac116b688380489c3690f6f65b282990c221f17Adam Cohen 79444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe up 795839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 796839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showNext(); 79796d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else { 79896d8d56302da81b24333b204e6d7f15064538036Adam Cohen showPrevious(); 799839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 800839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 80132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 802839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) { 80344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe up far enough, snap back down 804839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 805839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0; 806839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 807839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 808839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 809839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 810839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 81132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 812b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 8132794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress); 8142794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 8152794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 816839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 8172794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 818839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 819839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 82032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 82144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe down far enough, snap back up 822839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1; 823839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 824839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 825839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 826839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 827839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 828839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 8293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 830b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 8312794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = 8322794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder.ofFloat("YProgress",finalYProgress); 8332794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 8342794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 835839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 8362794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 837839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 83844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 83944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 84044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 84144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 84232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 84332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 84432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private class StackSlider { 84532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen View mView; 84632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mYProgress; 84732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mXProgress; 84832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 8493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int NORMAL_MODE = 0; 8503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int BEGINNING_OF_STACK_MODE = 1; 8513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int END_OF_STACK_MODE = 2; 8523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 8533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int mMode = NORMAL_MODE; 8543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 855b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider() { 856b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 857b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 858b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider(StackSlider copy) { 859b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mView = copy.mView; 860b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mYProgress = copy.mYProgress; 861b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mXProgress = copy.mXProgress; 8623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = copy.mMode; 863b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 864b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 86532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float cubic(float r) { 866839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f; 86732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 86832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 86932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float highlightAlphaInterpolator(float r) { 87032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.4f; 87132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r < pivot) { 872839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(r / pivot); 87332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 874839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(1 - (r - pivot) / (1 - pivot)); 87532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 87632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 87732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 87832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float viewAlphaInterpolator(float r) { 87932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.3f; 88032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r > pivot) { 881839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 88232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 88332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0; 88432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 88532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 88632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 887b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen private float rotationInterpolator(float r) { 888b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen float pivot = 0.2f; 889b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen if (r < pivot) { 890b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen return 0; 891b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } else { 892839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 893b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 894b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 895b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 89632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen void setView(View v) { 89732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mView = v; 89832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 89932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 90032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setYProgress(float r) { 90132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 90232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.min(1.0f, r); 90332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.max(0, r); 90432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 90532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mYProgress = r; 906a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen if (mView == null) return; 907a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen 90832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 90932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 91032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 911839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; 912839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 913488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen // We need to prevent any clipping issues which may arise by setting a layer type. 914488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen // This doesn't come for free however, so we only want to enable it when required. 915488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen if (Float.compare(0f, mYProgress) != 0 && Float.compare(1.0f, mYProgress) != 0) { 916488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen if (mView.getLayerType() == LAYER_TYPE_NONE) { 917488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen mView.setLayerType(LAYER_TYPE_HARDWARE, null); 918488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } 919488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } else { 920488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen if (mView.getLayerType() != LAYER_TYPE_NONE) { 921488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen mView.setLayerType(LAYER_TYPE_NONE, null); 922488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } 923488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } 924488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen 9253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen switch (mMode) { 9263d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case NORMAL_MODE: 927dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 928dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 9293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 9303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 931839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float alpha = viewAlphaInterpolator(1 - r); 9323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 9333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We make sure that views which can't be seen (have 0 alpha) are also invisible 9343d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so that they don't interfere with click events. 9353d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) { 9363d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(VISIBLE); 9373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (alpha == 0 && mView.getAlpha() != 0 9383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mView.getVisibility() == VISIBLE) { 9393d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(INVISIBLE); 9403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 941b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 9423d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setAlpha(alpha); 943839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 944839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 9453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 94696d8d56302da81b24333b204e6d7f15064538036Adam Cohen case END_OF_STACK_MODE: 947839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = r * 0.2f; 948dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 949dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 9503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 9513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 95296d8d56302da81b24333b204e6d7f15064538036Adam Cohen case BEGINNING_OF_STACK_MODE: 953839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = (1-r) * 0.2f; 954dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 955dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 9563d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 9573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 958b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 95932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 96032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 96132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setXProgress(float r) { 96232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 9633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.min(2.0f, r); 9643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.max(-2.0f, r); 96532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 96632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mXProgress = r; 96732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 968a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen if (mView == null) return; 96932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 97032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 97132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 9723d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r *= 0.2f; 973dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 974dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 97532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 97632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 9773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen void setMode(int mode) { 9783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = mode; 9793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 9803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 9813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForNeutralPosition() { 982839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, 0); 9833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 9843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 9853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForOffscreenPosition() { 986839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, 0); 987839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 988839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 989839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForNeutralPosition(float velocity) { 990839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, velocity); 991839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 992839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 993839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForOffscreenPosition(float velocity) { 994839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, velocity); 9953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 9963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 997839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private float getDuration(boolean invert, float velocity) { 9983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView != null) { 9993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 10003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 1001839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) + 1002839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Math.pow(viewLp.verticalOffset, 2)); 1003dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) + 1004dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen Math.pow(0.4f * mSlideAmount, 2)); 1005839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1006839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (velocity == 0) { 1007839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION; 1008839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 1009839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float duration = invert ? d / Math.abs(velocity) : 1010839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen (maxd - d) / Math.abs(velocity); 1011839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (duration < MINIMUM_ANIMATION_DURATION || 1012839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration > DEFAULT_ANIMATION_DURATION) { 1013839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(invert, 0); 1014839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 1015839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return duration; 1016839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1017839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 10183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 10193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return 0; 10203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 10213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 1022e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy // Used for animations 1023e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"UnusedDeclaration"}) 1024839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getYProgress() { 102532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mYProgress; 102632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 102732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1028e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy // Used for animations 1029e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"UnusedDeclaration"}) 1030839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getXProgress() { 103132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mXProgress; 103232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 103344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 103444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams createOrReuseLayoutParams(View v) { 10369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); 10379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (currentLp instanceof LayoutParams) { 10389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) currentLp; 10399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setHorizontalOffset(0); 10409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setVerticalOffset(0); 1041839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 1042839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 10439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return lp; 10449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return new LayoutParams(v); 104632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 104732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 10489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen @Override 10499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1050ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen checkForAndHandleDataChanged(); 10519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final int childCount = getChildCount(); 10539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen for (int i = 0; i < childCount; i++) { 10549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final View child = getChildAt(i); 10559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childRight = mPaddingLeft + child.getMeasuredWidth(); 10579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childBottom = mPaddingTop + child.getMeasuredHeight(); 10589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) child.getLayoutParams(); 10599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, 10619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); 10629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen onLayout(); 10659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 106726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen @Override 106826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen public void advance() { 106926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime; 1070c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen 1071c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (mAdapter == null) return; 1072ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen final int adapterCount = getCount(); 1073c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (adapterCount == 1 && mLoopViews) return; 1074c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen 107526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen if (mSwipeGestureType == GESTURE_NONE && 107626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) { 107726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen showNext(); 107826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen } 107926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen } 108026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen 1081839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private void measureChildren() { 1082839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int count = getChildCount(); 108336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 108436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int measuredWidth = getMeasuredWidth(); 108536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int measuredHeight = getMeasuredHeight(); 108636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 108736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childWidth = Math.round(measuredWidth*(1-PERSPECTIVE_SHIFT_FACTOR_X)) 1088026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen - mPaddingLeft - mPaddingRight; 108936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childHeight = Math.round(measuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR_Y)) 1090839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen - mPaddingTop - mPaddingBottom; 1091839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 109236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen int maxWidth = 0; 109336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen int maxHeight = 0; 109436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 1095839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen for (int i = 0; i < count; i++) { 1096839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final View child = getChildAt(i); 109736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), 109836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST)); 109936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 110036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (child != mHighlight && child != mClickFeedback) { 110136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childMeasuredWidth = child.getMeasuredWidth(); 110236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childMeasuredHeight = child.getMeasuredHeight(); 110336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (childMeasuredWidth > maxWidth) { 110436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen maxWidth = childMeasuredWidth; 110536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 110636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (childMeasuredHeight > maxHeight) { 110736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen maxHeight = childMeasuredHeight; 110836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 110936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 111036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 111136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 111236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftX = PERSPECTIVE_SHIFT_FACTOR_X * measuredWidth; 111336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftY = PERSPECTIVE_SHIFT_FACTOR_Y * measuredHeight; 111453838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen 111553838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen // If we have extra space, we try and spread the items out 111678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (maxWidth > 0 && count > 0 && maxWidth < childWidth) { 111736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftX = measuredWidth - maxWidth; 111836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 111936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 112078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (maxHeight > 0 && count > 0 && maxHeight < childHeight) { 112136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftY = measuredHeight - maxHeight; 1122839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1123839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1124839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1125839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen @Override 1126839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1127839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 1128839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 1129839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 1130839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 1131839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1132839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); 1133839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1134839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // We need to deal with the case where our parent hasn't told us how 1135839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // big we should be. In this case we should 1136026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float factorY = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_Y); 1137839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (heightSpecMode == MeasureSpec.UNSPECIFIED) { 1138839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen heightSpecSize = haveChildRefSize ? 1139026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen Math.round(mReferenceChildHeight * (1 + factorY)) + 1140839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingTop + mPaddingBottom : 0; 1141839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 1142189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (haveChildRefSize) { 1143026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen int height = Math.round(mReferenceChildHeight * (1 + factorY)) 1144189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn + mPaddingTop + mPaddingBottom; 1145189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (height <= heightSpecSize) { 1146189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize = height; 1147189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1148189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize |= MEASURED_STATE_TOO_SMALL; 1149ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen 1150189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1151189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1152189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize = 0; 1153189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1154839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1155839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1156026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float factorX = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_X); 1157839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (widthSpecMode == MeasureSpec.UNSPECIFIED) { 1158026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen widthSpecSize = haveChildRefSize ? 1159026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen Math.round(mReferenceChildWidth * (1 + factorX)) + 1160026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen mPaddingLeft + mPaddingRight : 0; 1161839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 1162189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (haveChildRefSize) { 1163189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight; 1164189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (width <= widthSpecSize) { 1165189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize = width; 1166189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1167189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize |= MEASURED_STATE_TOO_SMALL; 1168189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1169189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1170189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize = 0; 1171189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1172839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1173839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen setMeasuredDimension(widthSpecSize, heightSpecSize); 1174839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen measureChildren(); 1175839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1176839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 11779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen class LayoutParams extends ViewGroup.LayoutParams { 11789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int horizontalOffset; 11799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int verticalOffset; 11809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View mView; 1181d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect parentRect = new Rect(); 1182d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect invalidateRect = new Rect(); 1183d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final RectF invalidateRectf = new RectF(); 1184d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect globalInvalidateRect = new Rect(); 11859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(View view) { 11879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(0, 0); 1188839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 1189839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 11909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 11919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 11929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView = view; 11939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 11949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(Context c, AttributeSet attrs) { 11969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(c, attrs); 11979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 11989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 1199839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 1200839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 1201b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 1202b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 12039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void invalidateGlobalRegion(View v, Rect r) { 1204d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen // We need to make a new rect here, so as not to modify the one passed 1205d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.set(r); 12069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View p = v; 12079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!(v.getParent() != null && v.getParent() instanceof View)) return; 12089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean firstPass = true; 12109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen parentRect.set(0, 0, 0, 0); 12119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int depth = 0; 1212b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen while (p.getParent() != null && p.getParent() instanceof View 1213d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen && !parentRect.contains(globalInvalidateRect)) { 12149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!firstPass) { 1215d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop() 1216d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen - p.getScrollY()); 12179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen depth++; 12189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 12199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen firstPass = false; 12209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p = (View) p.getParent(); 1221b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen parentRect.set(p.getScrollX(), p.getScrollY(), 1222b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY()); 1223839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 12249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 12259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1226d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top, 1227d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.right, globalInvalidateRect.bottom); 1228d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen } 1229d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen 1230d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen Rect getInvalidateRect() { 1231d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen return invalidateRect; 1232d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen } 12339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1234d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen void resetInvalidateRect() { 12350ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRect.set(0, 0, 0, 0); 12369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 123732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1238a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase // This is public so that ObjectAnimator can access it 12399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setVerticalOffset(int newVerticalOffset) { 12400ac116b688380489c3690f6f65b282990c221f17Adam Cohen setOffsets(horizontalOffset, newVerticalOffset); 12419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 12429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setHorizontalOffset(int newHorizontalOffset) { 12440ac116b688380489c3690f6f65b282990c221f17Adam Cohen setOffsets(newHorizontalOffset, verticalOffset); 12450ac116b688380489c3690f6f65b282990c221f17Adam Cohen } 12460ac116b688380489c3690f6f65b282990c221f17Adam Cohen 12470ac116b688380489c3690f6f65b282990c221f17Adam Cohen public void setOffsets(int newHorizontalOffset, int newVerticalOffset) { 12480ac116b688380489c3690f6f65b282990c221f17Adam Cohen int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset; 12499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = newHorizontalOffset; 12500ac116b688380489c3690f6f65b282990c221f17Adam Cohen int verticalOffsetDelta = newVerticalOffset - verticalOffset; 12510ac116b688380489c3690f6f65b282990c221f17Adam Cohen verticalOffset = newVerticalOffset; 12529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mView != null) { 12549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.requestLayout(); 12550ac116b688380489c3690f6f65b282990c221f17Adam Cohen int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft()); 12560ac116b688380489c3690f6f65b282990c221f17Adam Cohen int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight()); 12570ac116b688380489c3690f6f65b282990c221f17Adam Cohen int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop()); 12580ac116b688380489c3690f6f65b282990c221f17Adam Cohen int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom()); 12590ac116b688380489c3690f6f65b282990c221f17Adam Cohen 12600ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRectf.set(left, top, right, bottom); 12619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xoffset = -invalidateRectf.left; 12639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yoffset = -invalidateRectf.top; 12649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(xoffset, yoffset); 12659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.getMatrix().mapRect(invalidateRectf); 12669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(-xoffset, -yoffset); 12679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12680ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRect.set((int) Math.floor(invalidateRectf.left), 12699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.floor(invalidateRectf.top), 12709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.right), 12719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.bottom)); 12729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateGlobalRegion(mView, invalidateRect); 12749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 12759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 127632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 127732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 12789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static class HolographicHelper { 12799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mHolographicPaint = new Paint(); 12809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mErasePaint = new Paint(); 1281839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private final Paint mBlurPaint = new Paint(); 12828baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private static final int RES_OUT = 0; 12838baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private static final int CLICK_FEEDBACK = 1; 12848baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private float mDensity; 1285e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private BlurMaskFilter mSmallBlurMaskFilter; 1286e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private BlurMaskFilter mLargeBlurMaskFilter; 1287e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Canvas mCanvas = new Canvas(); 1288e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Canvas mMaskCanvas = new Canvas(); 1289e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final int[] mTmpXY = new int[2]; 1290e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Matrix mIdentityMatrix = new Matrix(); 129132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1292dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen HolographicHelper(Context context) { 12938baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mDensity = context.getResources().getDisplayMetrics().density; 1294dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 12959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHolographicPaint.setFilterBitmap(true); 1296839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); 12979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 12989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setFilterBitmap(true); 1299e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy 1300e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL); 1301e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL); 13029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 13039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap createOutline(View v) { 13058baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen return createOutline(v, RES_OUT); 13068baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 13078baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 13088baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen Bitmap createOutline(View v, int type) { 13098baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (type == RES_OUT) { 13108baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mHolographicPaint.setColor(0xff6699ff); 1311e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mBlurPaint.setMaskFilter(mSmallBlurMaskFilter); 13128baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } else if (type == CLICK_FEEDBACK) { 13138baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mHolographicPaint.setColor(0x886699ff); 1314e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mBlurPaint.setMaskFilter(mLargeBlurMaskFilter); 13158baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 13168baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 13179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) { 13189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return null; 13199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 13209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), 13229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap.Config.ARGB_8888); 1323e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mCanvas.setBitmap(bitmap); 132432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 13259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float rotationX = v.getRotationX(); 1326839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float rotation = v.getRotation(); 1327839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float translationY = v.getTranslationY(); 1328026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float translationX = v.getTranslationX(); 13299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(0); 1330839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(0); 1331839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(0); 1332026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen v.setTranslationX(0); 1333e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy v.draw(mCanvas); 13349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(rotationX); 1335839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(rotation); 1336839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(translationY); 1337026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen v.setTranslationX(translationX); 13389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1339e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy drawOutline(mCanvas, bitmap); 13409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return bitmap; 13419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 13429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void drawOutline(Canvas dest, Bitmap src) { 1344e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy final int[] xy = mTmpXY; 1345839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Bitmap mask = src.extractAlpha(mBlurPaint, xy); 1346e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mMaskCanvas.setBitmap(mask); 1347e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint); 13489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.drawColor(0, PorterDuff.Mode.CLEAR); 1349e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy dest.setMatrix(mIdentityMatrix); 1350839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint); 13519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mask.recycle(); 13529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 135332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 1354ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen} 1355