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; 2326f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohenimport android.content.res.TypedArray; 2432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap; 25839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter; 2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas; 2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix; 2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint; 2932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff; 3032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode; 3144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect; 329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF; 33d51bbb5b56446519db88f49f2682da782b0069acAdam Cohenimport android.graphics.Region; 34839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter; 3548d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganovimport android.os.Bundle; 3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet; 3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log; 38a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohenimport android.view.InputDevice; 3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent; 4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker; 4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View; 4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration; 4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup; 448a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganovimport android.view.accessibility.AccessibilityEvent; 458a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganovimport android.view.accessibility.AccessibilityNodeInfo; 46b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator; 4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView; 4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView 5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/** 5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe 5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children. 5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator { 5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final String TAG = "StackView"; 5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Default animation parameters 5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 60e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy private static final int DEFAULT_ANIMATION_DURATION = 400; 61e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy private static final int MINIMUM_ANIMATION_DURATION = 50; 62c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen private static final int STACK_RELAYOUT_DURATION = 100; 6344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 65839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Parameters effecting the perspective visuals 66839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 67026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen private static final float PERSPECTIVE_SHIFT_FACTOR_Y = 0.1f; 68026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen private static final float PERSPECTIVE_SHIFT_FACTOR_X = 0.1f; 69026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 7036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mPerspectiveShiftX; 7136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mPerspectiveShiftY; 7236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mNewPerspectiveShiftX; 7336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private float mNewPerspectiveShiftY; 7436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 75e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"FieldCanBeLocal"}) 7678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen private static final float PERSPECTIVE_SCALE_FACTOR = 0f; 77839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 78839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 79839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Represent the two possible stack modes, one where items slide up, and the other 80839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * where items slide down. The perspective is also inverted between these two modes. 81839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 82839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_UP = 0; 83839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_DOWN = 1; 84839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 85839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 8644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These specify the different gesture states 8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 885b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_NONE = 0; 895b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_UP = 1; 905b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_DOWN = 2; 9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Specifies how far you need to swipe (up or down) before it 9444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * will be consider a completed gesture when you lift your finger 9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 96a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen private static final float SWIPE_THRESHOLD_RATIO = 0.2f; 97a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen 98a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen /** 99a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen * Specifies the total distance, relative to the size of the stack, 100a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen * that views will be slid, either up or down 101a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen */ 1025b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SLIDE_UP_RATIO = 0.7f; 10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Sentinel value for no current active pointer. 10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Used by {@link #mActivePointerId}. 10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private static final int INVALID_POINTER = -1; 10944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 111839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Number of active views in the stack. One fewer view is actually visible, as one is hidden. 112839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 113839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int NUM_ACTIVE_VIEWS = 5; 114839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 115dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private static final int FRAME_PADDING = 4; 116839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 117e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Rect mTouchRect = new Rect(); 118e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy 11926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000; 12026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen 1214725a90e8ea82799fb88fd27db5bb16969ac5abfRomain Guy private static final long MIN_TIME_BETWEEN_SCROLLS = 100; 122a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen 123839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These variables are all related to the current state of touch interaction 12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * with the stack 12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialY; 12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialX; 12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mActivePointerId; 13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mYVelocity = 0; 13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeGestureType = GESTURE_NONE; 132dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mSlideAmount; 13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeThreshold; 13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mTouchSlop; 13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mMaximumVelocity; 13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private VelocityTracker mVelocityTracker; 1373352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen private boolean mTransitionIsSetup = false; 13826f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen private int mResOutColor; 13926f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen private int mClickColor; 14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static HolographicHelper sHolographicHelper; 14232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private ImageView mHighlight; 1438baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private ImageView mClickFeedback; 1448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private boolean mClickFeedbackIsValid = false; 14532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private StackSlider mStackSlider; 14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mFirstLayoutHappened = false; 14726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen private long mLastInteractionTime = 0; 148a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen private long mLastScrollTime; 149839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private int mStackMode; 150dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mFramePadding; 1510ac116b688380489c3690f6f65b282990c221f17Adam Cohen private final Rect stackInvalidateRect = new Rect(); 15244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 153abc8b50a26282689c66562cf619563744d2855d1Winson Chung /** 154abc8b50a26282689c66562cf619563744d2855d1Winson Chung * {@inheritDoc} 155abc8b50a26282689c66562cf619563744d2855d1Winson Chung */ 15644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context) { 15726f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen this(context, null); 15844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 15944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 160abc8b50a26282689c66562cf619563744d2855d1Winson Chung /** 161abc8b50a26282689c66562cf619563744d2855d1Winson Chung * {@inheritDoc} 162abc8b50a26282689c66562cf619563744d2855d1Winson Chung */ 16344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context, AttributeSet attrs) { 16426f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen this(context, attrs, com.android.internal.R.attr.stackViewStyle); 16526f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen } 16626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen 167abc8b50a26282689c66562cf619563744d2855d1Winson Chung /** 168abc8b50a26282689c66562cf619563744d2855d1Winson Chung * {@inheritDoc} 169abc8b50a26282689c66562cf619563744d2855d1Winson Chung */ 17026f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen public StackView(Context context, AttributeSet attrs, int defStyleAttr) { 171617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette this(context, attrs, defStyleAttr, 0); 172617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette } 173617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette 174617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette /** 175617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette * {@inheritDoc} 176617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette */ 177617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette public StackView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 178617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette super(context, attrs, defStyleAttr, defStyleRes); 179617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette final TypedArray a = context.obtainStyledAttributes( 180617feb99a06e7ffb3894e86a286bf30e085f321aAlan Viverette attrs, com.android.internal.R.styleable.StackView, defStyleAttr, defStyleRes); 18126f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen 18226f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen mResOutColor = a.getColor( 18326f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen com.android.internal.R.styleable.StackView_resOutColor, 0); 18426f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen mClickColor = a.getColor( 18526f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen com.android.internal.R.styleable.StackView_clickColor, 0); 18626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen 18726f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen a.recycle(); 18844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 18944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 19044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 19144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void initStackView() { 19296d8d56302da81b24333b204e6d7f15064538036Adam Cohen configureViewAnimator(NUM_ACTIVE_VIEWS, 1); 19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setStaticTransformationsEnabled(true); 19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 195b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mTouchSlop = configuration.getScaledTouchSlop(); 19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 19744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 19832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 19932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight = new ImageView(getContext()); 20032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.setLayoutParams(new LayoutParams(mHighlight)); 20132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight)); 2028baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 2038baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback = new ImageView(getContext()); 2048baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback)); 2058baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback)); 2068baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(INVISIBLE); 2078baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 20832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider = new StackSlider(); 20932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 2109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (sHolographicHelper == null) { 211dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen sHolographicHelper = new HolographicHelper(mContext); 21232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 2139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipChildren(false); 2149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipToPadding(false); 2151480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen 216839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // This sets the form of the StackView, which is currently to have the perspective-shifted 217839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // views above the active view, and have items slide down when sliding out. The opposite is 218839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // available by using ITEMS_SLIDE_UP. 219839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mStackMode = ITEMS_SLIDE_DOWN; 220839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 2211480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // This is a flag to indicate the the stack is loading for the first time 2221480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen mWhichChild = -1; 223dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 224dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // Adjust the frame padding based on the density, since the highlight changes based 225dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // on the density 226dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen final float density = mContext.getResources().getDisplayMetrics().density; 227dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mFramePadding = (int) Math.ceil(density * FRAME_PADDING); 22844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 22944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 23044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 23144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Animate the views between different relative indexes within the {@link AdapterViewAnimator} 23244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 23378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen void transformViewForTransition(int fromIndex, int toIndex, final View view, boolean animate) { 23478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (!animate) { 23578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelSliderAnimator(); 23678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen view.setRotationX(0f); 23778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 23878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setVerticalOffset(0); 23978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setHorizontalOffset(0); 24069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen } 24169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen 242502045849f760f018f662d752020aef32d4cecd9Adam Cohen if (fromIndex == -1 && toIndex == getNumActiveViews() -1) { 243502045849f760f018f662d752020aef32d4cecd9Adam Cohen transformViewAtIndex(toIndex, view, false); 244b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(VISIBLE); 245c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen view.setAlpha(1.0f); 24696d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (fromIndex == 0 && toIndex == 1) { 24744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item in 24878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelSliderAnimator(); 24944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setVisibility(VISIBLE); 25032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 251839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity)); 252b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 25369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen animationSlider.setView(view); 25478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 25578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (animate) { 25678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f); 25778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 25878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator slideIn = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 25978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideInX, slideInY); 26078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideIn.setDuration(duration); 26178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideIn.setInterpolator(new LinearInterpolator()); 26278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setSliderAnimator(slideIn); 26378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideIn.start(); 26478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else { 26578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setYProgress(0f); 26678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setXProgress(0f); 26778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 26896d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (fromIndex == 1 && toIndex == 0) { 26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item out 27078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelSliderAnimator(); 271839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity)); 27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 273b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 27469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen animationSlider.setView(view); 27578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (animate) { 27678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f); 27778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 27878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator slideOut = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 27978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOutX, slideOutY); 28078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOut.setDuration(duration); 28178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOut.setInterpolator(new LinearInterpolator()); 28278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setSliderAnimator(slideOut); 28378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen slideOut.start(); 28478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else { 28578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setYProgress(1.0f); 28678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen animationSlider.setXProgress(0f); 28778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 28869d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen } else if (toIndex == 0) { 28944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Make sure this view that is "waiting in the wings" is invisible 29044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0.0f); 291b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(INVISIBLE); 29278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else if ((fromIndex == 0 || fromIndex == 1) && toIndex > 1) { 29369d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen view.setVisibility(VISIBLE); 29469d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen view.setAlpha(1.0f); 29578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen view.setRotationX(0f); 29678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 29778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setVerticalOffset(0); 29878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen lp.setHorizontalOffset(0); 299c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen } else if (fromIndex == -1) { 300c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen view.setAlpha(1.0f); 301c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen view.setVisibility(VISIBLE); 30244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (toIndex == -1) { 30378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (animate) { 304c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen postDelayed(new Runnable() { 305c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen public void run() { 306c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen view.setAlpha(0); 307c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen } 308c798b6688aa1076de96a70c2add20dcf745df800Adam Cohen }, STACK_RELAYOUT_DURATION); 30978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } else { 31078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen view.setAlpha(0f); 31178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 31244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 313839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 314839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // Implement the faked perspective 315839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (toIndex != -1) { 31678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen transformViewAtIndex(toIndex, view, animate); 317f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 318f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 319839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 32036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen private void transformViewAtIndex(int index, final View view, boolean animate) { 32136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float maxPerspectiveShiftY = mPerspectiveShiftY; 32236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float maxPerspectiveShiftX = mPerspectiveShiftX; 323026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 324c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) { 325c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen index = mMaxNumActiveViews - index - 1; 326c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (index == mMaxNumActiveViews - 1) index--; 327c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } else { 328c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen index--; 329c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (index < 0) index++; 330c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } 331026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 332026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float r = (index * 1.0f) / (mMaxNumActiveViews - 2); 333026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 33436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r); 335026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 336c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen float perspectiveTranslationY = r * maxPerspectiveShiftY; 337c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen float scaleShiftCorrectionY = (scale - 1) * 338026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR_Y) / 2.0f); 33936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float transY = perspectiveTranslationY + scaleShiftCorrectionY; 340026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 341026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float perspectiveTranslationX = (1 - r) * maxPerspectiveShiftX; 342026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float scaleShiftCorrectionX = (1 - scale) * 343026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen (getMeasuredWidth() * (1 - PERSPECTIVE_SHIFT_FACTOR_X) / 2.0f); 34436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final float transX = perspectiveTranslationX + scaleShiftCorrectionX; 34536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 346c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen // If this view is currently being animated for a certain position, we need to cancel 34769d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen // this animation so as not to interfere with the new transformation. 34878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (view instanceof StackFrame) { 34978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).cancelTransformAnimator(); 35069d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen } 35169d66e00136f67332c992326a7b2eb334eeb32a2Adam Cohen 35236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (animate) { 35336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", transX); 35436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY); 35536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder scalePropX = PropertyValuesHolder.ofFloat("scaleX", scale); 35636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen PropertyValuesHolder scalePropY = PropertyValuesHolder.ofFloat("scaleY", scale); 35736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 35836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(view, scalePropX, scalePropY, 35936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen translationY, translationX); 360c0b53be0c92cc1102c733beef2ada7480950f90eAdam Cohen oa.setDuration(STACK_RELAYOUT_DURATION); 36178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (view instanceof StackFrame) { 36278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ((StackFrame) view).setTransformAnimator(oa); 36378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 36436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen oa.start(); 36536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } else { 36636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setTranslationX(transX); 36736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setTranslationY(transY); 36836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setScaleX(scale); 36936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen view.setScaleY(scale); 37036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 371026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen } 372026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen 3733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen private void setupStackSlider(View v, int mode) { 3743352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setMode(mode); 3753352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 37626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen mHighlight.setImageBitmap(sHolographicHelper.createResOutline(v, mResOutColor)); 3773352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setRotation(v.getRotation()); 3783352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setTranslationY(v.getTranslationY()); 379026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen mHighlight.setTranslationX(v.getTranslationX()); 3803352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.bringToFront(); 3813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen v.bringToFront(); 3823352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setView(v); 3833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 3843352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen v.setVisibility(VISIBLE); 3853352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 3863352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 3873352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 3880e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 3890e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 3900e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 3913352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @Override 3923352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @android.view.RemotableViewMethod 3933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen public void showNext() { 3940ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (mSwipeGestureType != GESTURE_NONE) return; 3953352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (!mTransitionIsSetup) { 3963352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen View v = getViewAtRelativeIndex(1); 3973352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 3983352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, StackSlider.NORMAL_MODE); 3993352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setYProgress(0); 4003352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setXProgress(0); 4013352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4023352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4033352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen super.showNext(); 4043352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4053352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 4060e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 4070e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 4080e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 4093352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @Override 4103352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @android.view.RemotableViewMethod 4113352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen public void showPrevious() { 4120ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (mSwipeGestureType != GESTURE_NONE) return; 4133352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (!mTransitionIsSetup) { 4143352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen View v = getViewAtRelativeIndex(0); 4153352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 4163352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, StackSlider.NORMAL_MODE); 4173352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setYProgress(1); 4183352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setXProgress(0); 4193352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4203352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4213352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen super.showPrevious(); 4223352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 4233352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 42496d8d56302da81b24333b204e6d7f15064538036Adam Cohen @Override 425ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen void showOnly(int childIndex, boolean animate) { 426ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen super.showOnly(childIndex, animate); 42796d8d56302da81b24333b204e6d7f15064538036Adam Cohen 42896d8d56302da81b24333b204e6d7f15064538036Adam Cohen // Here we need to make sure that the z-order of the children is correct 4293352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) { 43096d8d56302da81b24333b204e6d7f15064538036Adam Cohen int index = modulo(i, getWindowSize()); 431d38a0cee2074fcf8005fc0d746aad282941a39b8Adam Cohen ViewAndMetaData vm = mViewsMap.get(index); 432d38a0cee2074fcf8005fc0d746aad282941a39b8Adam Cohen if (vm != null) { 4330ac116b688380489c3690f6f65b282990c221f17Adam Cohen View v = mViewsMap.get(index).view; 4340ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (v != null) v.bringToFront(); 4350ac116b688380489c3690f6f65b282990c221f17Adam Cohen } 43696d8d56302da81b24333b204e6d7f15064538036Adam Cohen } 43778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (mHighlight != null) { 43878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen mHighlight.bringToFront(); 43978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 4403352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mTransitionIsSetup = false; 4418baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedbackIsValid = false; 4428baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4438baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 4448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void updateClickFeedback() { 4458baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (!mClickFeedbackIsValid) { 4469c295482dd739e80dd49ea0dd3102ad6be6742ddAdam Cohen View v = getViewAtRelativeIndex(1); 4478baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (v != null) { 44826f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen mClickFeedback.setImageBitmap( 44926f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen sHolographicHelper.createClickOutline(v, mClickColor)); 4508baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setTranslationX(v.getTranslationX()); 4518baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setTranslationY(v.getTranslationY()); 4528baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4538baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedbackIsValid = true; 4548baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4558baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4568baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 4578baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen @Override 4588baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void showTapFeedback(View v) { 4598baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen updateClickFeedback(); 4608baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(VISIBLE); 4618baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.bringToFront(); 4628baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen invalidate(); 4638baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 4648baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 4658baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen @Override 4668baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void hideTapFeedback(View v) { 4678baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(INVISIBLE); 4688baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen invalidate(); 46996d8d56302da81b24333b204e6d7f15064538036Adam Cohen } 47096d8d56302da81b24333b204e6d7f15064538036Adam Cohen 471f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen private void updateChildTransforms() { 47296d8d56302da81b24333b204e6d7f15064538036Adam Cohen for (int i = 0; i < getNumActiveViews(); i++) { 473f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen View v = getViewAtRelativeIndex(i); 474f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen if (v != null) { 47536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen transformViewAtIndex(i, v, false); 476f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 477839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 47844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 47944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 48078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen private static class StackFrame extends FrameLayout { 48178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen WeakReference<ObjectAnimator> transformAnimator; 48278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen WeakReference<ObjectAnimator> sliderAnimator; 48378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 48478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen public StackFrame(Context context) { 48578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen super(context); 48678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 48778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 48878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen void setTransformAnimator(ObjectAnimator oa) { 48978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen transformAnimator = new WeakReference<ObjectAnimator>(oa); 49078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 49178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 49278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen void setSliderAnimator(ObjectAnimator oa) { 49378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen sliderAnimator = new WeakReference<ObjectAnimator>(oa); 49478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 49578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 49678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen boolean cancelTransformAnimator() { 49778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (transformAnimator != null) { 49878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator oa = transformAnimator.get(); 49978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (oa != null) { 50078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen oa.cancel(); 50178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return true; 50278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 50378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 50478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return false; 50578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 50678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 50778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen boolean cancelSliderAnimator() { 50878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (sliderAnimator != null) { 50978db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen ObjectAnimator oa = sliderAnimator.get(); 51078db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (oa != null) { 51178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen oa.cancel(); 51278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return true; 51378db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51478db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51578db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen return false; 51678db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen } 51878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 519dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen @Override 520dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen FrameLayout getFrameForChild() { 52178db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen StackFrame fl = new StackFrame(mContext); 522dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding); 523dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen return fl; 524dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen } 525dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 52644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 52744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Apply any necessary tranforms for the child that is being added. 52844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 52944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void applyTransformForChildAtIndex(View child, int relativeIndex) { 53044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 53144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 53244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 5339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void dispatchDraw(Canvas canvas) { 534321aa2b0258691c55843dff919eb2705d069167aAdam Cohen boolean expandClipRegion = false; 535321aa2b0258691c55843dff919eb2705d069167aAdam Cohen 5360ac116b688380489c3690f6f65b282990c221f17Adam Cohen canvas.getClipBounds(stackInvalidateRect); 537d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen final int childCount = getChildCount(); 538d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen for (int i = 0; i < childCount; i++) { 539e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen final View child = getChildAt(i); 540e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen LayoutParams lp = (LayoutParams) child.getLayoutParams(); 541e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen if ((lp.horizontalOffset == 0 && lp.verticalOffset == 0) || 542e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen child.getAlpha() == 0f || child.getVisibility() != VISIBLE) { 543e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen lp.resetInvalidateRect(); 544e86ff4d56145d875c13a1637833f9f55d79febc9Adam Cohen } 545321aa2b0258691c55843dff919eb2705d069167aAdam Cohen Rect childInvalidateRect = lp.getInvalidateRect(); 546321aa2b0258691c55843dff919eb2705d069167aAdam Cohen if (!childInvalidateRect.isEmpty()) { 547321aa2b0258691c55843dff919eb2705d069167aAdam Cohen expandClipRegion = true; 548321aa2b0258691c55843dff919eb2705d069167aAdam Cohen stackInvalidateRect.union(childInvalidateRect); 549321aa2b0258691c55843dff919eb2705d069167aAdam Cohen } 550321aa2b0258691c55843dff919eb2705d069167aAdam Cohen } 551321aa2b0258691c55843dff919eb2705d069167aAdam Cohen 552321aa2b0258691c55843dff919eb2705d069167aAdam Cohen // We only expand the clip bounds if necessary. 553321aa2b0258691c55843dff919eb2705d069167aAdam Cohen if (expandClipRegion) { 554321aa2b0258691c55843dff919eb2705d069167aAdam Cohen canvas.save(Canvas.CLIP_SAVE_FLAG); 555321aa2b0258691c55843dff919eb2705d069167aAdam Cohen canvas.clipRect(stackInvalidateRect, Region.Op.UNION); 556321aa2b0258691c55843dff919eb2705d069167aAdam Cohen super.dispatchDraw(canvas); 557321aa2b0258691c55843dff919eb2705d069167aAdam Cohen canvas.restore(); 558321aa2b0258691c55843dff919eb2705d069167aAdam Cohen } else { 559321aa2b0258691c55843dff919eb2705d069167aAdam Cohen super.dispatchDraw(canvas); 56044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 5619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 56244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private void onLayout() { 56444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mFirstLayoutHappened) { 56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mFirstLayoutHappened = true; 56653838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen updateChildTransforms(); 56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 56836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 569c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen final int newSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight()); 570c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen if (mSlideAmount != newSlideAmount) { 571c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen mSlideAmount = newSlideAmount; 572c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * newSlideAmount); 573c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen } 574c6a4716e24e6310efae78d04d86cc3369c2023b2Adam Cohen 57536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (Float.compare(mPerspectiveShiftY, mNewPerspectiveShiftY) != 0 || 57636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen Float.compare(mPerspectiveShiftX, mNewPerspectiveShiftX) != 0) { 57778db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen 57836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mPerspectiveShiftY = mNewPerspectiveShiftY; 57936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mPerspectiveShiftX = mNewPerspectiveShiftX; 58053838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen updateChildTransforms(); 58136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 58244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 58344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 584a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen @Override 585a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen public boolean onGenericMotionEvent(MotionEvent event) { 586a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 587a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen switch (event.getAction()) { 588a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen case MotionEvent.ACTION_SCROLL: { 589a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen final float vscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 590a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen if (vscroll < 0) { 591a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen pacedScroll(false); 592a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen return true; 593a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } else if (vscroll > 0) { 594a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen pacedScroll(true); 595a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen return true; 596a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 597a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 598a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 599a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 600a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen return super.onGenericMotionEvent(event); 601a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 602a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen 603a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen // This ensures that the frequency of stack flips caused by scrolls is capped 604a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen private void pacedScroll(boolean up) { 605a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen long timeSinceLastScroll = System.currentTimeMillis() - mLastScrollTime; 606a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen if (timeSinceLastScroll > MIN_TIME_BETWEEN_SCROLLS) { 607a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen if (up) { 608a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen showPrevious(); 609a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } else { 610a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen showNext(); 611a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 612a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen mLastScrollTime = System.currentTimeMillis(); 613a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 614a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen } 615a8a7c92b3689cb90d72d03c0162bca848b19c392Adam Cohen 6160e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 6170e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 6180e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 61944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 62044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onInterceptTouchEvent(MotionEvent ev) { 62144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 62244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch(action & MotionEvent.ACTION_MASK) { 62344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_DOWN: { 62444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mActivePointerId == INVALID_POINTER) { 62544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX = ev.getX(); 62644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY = ev.getY(); 62744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(0); 62844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 62944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 63044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 63144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 63244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 63344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 63444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 63544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 63644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 63744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 63844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 63944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 64044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 64132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 64244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 64344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 64444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 64544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 64644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 64744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 64844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: 64944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 65044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 65144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 65244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 65344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 65444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 65544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return mSwipeGestureType != GESTURE_NONE; 65644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 65744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 65832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private void beginGestureIfNeeded(float deltaY) { 65932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 660c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen final int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 66132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen cancelLongPress(); 66232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen requestDisallowInterceptTouchEvent(true); 66332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 664d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen if (mAdapter == null) return; 665ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen final int adapterCount = getCount(); 666d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen 667839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int activeIndex; 668839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 66996d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1; 670839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 67196d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0; 672839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 67332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 674c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen boolean endOfStack = mLoopViews && adapterCount == 1 && 675c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen ((mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_UP) || 676c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen (mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_DOWN)); 677c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen boolean beginningOfStack = mLoopViews && adapterCount == 1 && 678c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen ((mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_UP) || 679c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen (mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_DOWN)); 680c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen 6813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen int stackMode; 682c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (mLoopViews && !beginningOfStack && !endOfStack) { 6833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.NORMAL_MODE; 684c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == -1 || beginningOfStack) { 68596d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex++; 6863352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.BEGINNING_OF_STACK_MODE; 687c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == adapterCount - 1 || endOfStack) { 6883352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.END_OF_STACK_MODE; 6893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else { 6903352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.NORMAL_MODE; 69132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 6923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6933352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE; 6943352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 6953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen View v = getViewAtRelativeIndex(activeIndex); 6963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (v == null) return; 6973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6983352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, stackMode); 6993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 7003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We only register this gesture if we've made it this far without a problem 7013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mSwipeGestureType = swipeGestureType; 702a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen cancelHandleClick(); 70332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 70432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 70532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7060e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen /** 7070e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen * {@inheritDoc} 7080e2de6d7187ef67ec00a2f2544450caa4a239c39Adam Cohen */ 70944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 71044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onTouchEvent(MotionEvent ev) { 711a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen super.onTouchEvent(ev); 712a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen 71344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 71444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 71544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 71644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 71744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 71844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 71944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 72044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 72144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 72232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float newX = ev.getX(pointerIndex); 72344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 72432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float deltaX = newX - mInitialX; 72544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker == null) { 72644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = VelocityTracker.obtain(); 72744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 72844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.addMovement(ev); 72944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 73044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch (action & MotionEvent.ACTION_MASK) { 73144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 73232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 73332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 734dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float rx = deltaX / (mSlideAmount * 1.0f); 73532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 736dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 737839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 73832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(1 - r); 73932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 74032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 74132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 742dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 743839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 74432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(r); 74532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 74632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 74744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 74844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 74944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: { 75144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 75244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 75344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 75544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 75644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 75744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 75944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 76044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 76144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 76244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 76344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 76444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 76544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 76644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 76744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void onSecondaryPointerUp(MotionEvent ev) { 76844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int activePointerIndex = ev.getActionIndex(); 76944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int pointerId = ev.getPointerId(activePointerIndex); 77044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerId == mActivePointerId) { 77144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 77296d8d56302da81b24333b204e6d7f15064538036Adam Cohen int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1; 77344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 77444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(activeViewIndex); 77544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v == null) return; 77644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 77744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Our primary pointer has gone up -- let's see if we can find 77844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // another pointer on the view. If so, then we should replace 77944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // our primary pointer with this new pointer and adjust things 78044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so that the view doesn't jump 78144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (int index = 0; index < ev.getPointerCount(); index++) { 78244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (index != activePointerIndex) { 78344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 78444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float x = ev.getX(index); 78544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float y = ev.getY(index); 78644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 787e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 788e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy if (mTouchRect.contains(Math.round(x), Math.round(y))) { 78944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldX = ev.getX(activePointerIndex); 79044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldY = ev.getY(activePointerIndex); 79144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 79244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // adjust our frame of reference to avoid a jump 79344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY += (y - oldY); 79444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX += (x - oldX); 79544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 79644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(index); 79744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 79844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.clear(); 79944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 80044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ok, we're good, we found a new pointer which is touching the active view 80144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return; 80244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 80344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 80444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 80544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if we made it this far, it means we didn't find a satisfactory new pointer :(, 8063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so end the gesture 80744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 80844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 80944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 81044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 81144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void handlePointerUp(MotionEvent ev) { 81244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 81344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 81444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int deltaY = (int) (newY - mInitialY); 81526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen mLastInteractionTime = System.currentTimeMillis(); 81644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 8173d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mVelocityTracker != null) { 8183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 8193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); 8203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 82144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 82244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 82344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.recycle(); 82444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = null; 82544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 82644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 8273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN 8283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 8290ac116b688380489c3690f6f65b282990c221f17Adam Cohen // We reset the gesture variable, because otherwise we will ignore showPrevious() / 8300ac116b688380489c3690f6f65b282990c221f17Adam Cohen // showNext(); 8310ac116b688380489c3690f6f65b282990c221f17Adam Cohen mSwipeGestureType = GESTURE_NONE; 8320ac116b688380489c3690f6f65b282990c221f17Adam Cohen 83344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe down 834839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 835839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showPrevious(); 83696d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else { 83796d8d56302da81b24333b204e6d7f15064538036Adam Cohen showNext(); 838839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 83932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 8403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP 8413d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 8420ac116b688380489c3690f6f65b282990c221f17Adam Cohen // We reset the gesture variable, because otherwise we will ignore showPrevious() / 8430ac116b688380489c3690f6f65b282990c221f17Adam Cohen // showNext(); 8440ac116b688380489c3690f6f65b282990c221f17Adam Cohen mSwipeGestureType = GESTURE_NONE; 8450ac116b688380489c3690f6f65b282990c221f17Adam Cohen 84644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe up 847839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 848839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showNext(); 84996d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else { 85096d8d56302da81b24333b204e6d7f15064538036Adam Cohen showPrevious(); 851839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 852839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 85332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 854839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) { 85544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe up far enough, snap back down 856839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 857839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0; 858839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 859839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 860839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 861839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 862839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 86332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 864b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 8652794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress); 8662794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 8672794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 868839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 8692794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 870839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 871839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 87232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 87344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe down far enough, snap back up 874839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1; 875839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 876839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 877839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 878839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 879839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 880839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 8813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 882b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 8832794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = 8842794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder.ofFloat("YProgress",finalYProgress); 8852794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 8862794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 887839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 8882794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 889839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 89044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 89144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 89244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 89344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 89432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 89532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 89632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private class StackSlider { 89732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen View mView; 89832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mYProgress; 89932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mXProgress; 90032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 9013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int NORMAL_MODE = 0; 9023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int BEGINNING_OF_STACK_MODE = 1; 9033d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int END_OF_STACK_MODE = 2; 9043d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 9053d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int mMode = NORMAL_MODE; 9063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 907b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider() { 908b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 909b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 910b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider(StackSlider copy) { 911b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mView = copy.mView; 912b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mYProgress = copy.mYProgress; 913b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mXProgress = copy.mXProgress; 9143d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = copy.mMode; 915b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 916b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 91732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float cubic(float r) { 918839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f; 91932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 92032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 92132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float highlightAlphaInterpolator(float r) { 92232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.4f; 92332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r < pivot) { 924839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(r / pivot); 92532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 926839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(1 - (r - pivot) / (1 - pivot)); 92732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 92832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 92932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 93032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float viewAlphaInterpolator(float r) { 93132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.3f; 93232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r > pivot) { 933839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 93432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 93532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0; 93632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 93732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 93832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 939b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen private float rotationInterpolator(float r) { 940b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen float pivot = 0.2f; 941b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen if (r < pivot) { 942b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen return 0; 943b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } else { 944839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 945b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 946b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 947b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 94832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen void setView(View v) { 94932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mView = v; 95032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 95132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 95232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setYProgress(float r) { 95332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 95432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.min(1.0f, r); 95532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.max(0, r); 95632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 95732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mYProgress = r; 958a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen if (mView == null) return; 959a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen 96032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 96132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 96232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 963839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; 964839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 965488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen // We need to prevent any clipping issues which may arise by setting a layer type. 966488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen // This doesn't come for free however, so we only want to enable it when required. 967488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen if (Float.compare(0f, mYProgress) != 0 && Float.compare(1.0f, mYProgress) != 0) { 968488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen if (mView.getLayerType() == LAYER_TYPE_NONE) { 969488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen mView.setLayerType(LAYER_TYPE_HARDWARE, null); 970488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } 971488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } else { 972488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen if (mView.getLayerType() != LAYER_TYPE_NONE) { 973488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen mView.setLayerType(LAYER_TYPE_NONE, null); 974488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } 975488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen } 976488672148c30b17c0ea7fa0f4af3897ad51c566cAdam Cohen 9773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen switch (mMode) { 9783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case NORMAL_MODE: 979dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 980dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 9813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 9823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 983839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float alpha = viewAlphaInterpolator(1 - r); 9843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 9853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We make sure that views which can't be seen (have 0 alpha) are also invisible 9863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so that they don't interfere with click events. 9873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) { 9883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(VISIBLE); 9893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (alpha == 0 && mView.getAlpha() != 0 9903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mView.getVisibility() == VISIBLE) { 9913d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(INVISIBLE); 9923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 993b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 9943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setAlpha(alpha); 995839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 996839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 9973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 99896d8d56302da81b24333b204e6d7f15064538036Adam Cohen case END_OF_STACK_MODE: 999839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = r * 0.2f; 1000dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 1001dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 10023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 10033d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 100496d8d56302da81b24333b204e6d7f15064538036Adam Cohen case BEGINNING_OF_STACK_MODE: 1005839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = (1-r) * 0.2f; 1006dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 1007dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 10083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 10093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 1010b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 101132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 101232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 101332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setXProgress(float r) { 101432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 10153d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.min(2.0f, r); 10163d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.max(-2.0f, r); 101732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 101832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mXProgress = r; 101932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1020a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen if (mView == null) return; 102132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 102232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 102332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 10243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r *= 0.2f; 1025dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 1026dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 102732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 102832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 10293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen void setMode(int mode) { 10303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = mode; 10313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 10323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 10333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForNeutralPosition() { 1034839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, 0); 10353d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 10363d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 10373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForOffscreenPosition() { 1038839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, 0); 1039839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1040839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1041839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForNeutralPosition(float velocity) { 1042839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, velocity); 1043839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1044839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1045839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForOffscreenPosition(float velocity) { 1046839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, velocity); 10473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 10483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 1049839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private float getDuration(boolean invert, float velocity) { 10503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView != null) { 10513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 10523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 1053839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) + 1054839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Math.pow(viewLp.verticalOffset, 2)); 1055dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) + 1056dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen Math.pow(0.4f * mSlideAmount, 2)); 1057839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1058839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (velocity == 0) { 1059839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION; 1060839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 1061839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float duration = invert ? d / Math.abs(velocity) : 1062839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen (maxd - d) / Math.abs(velocity); 1063839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (duration < MINIMUM_ANIMATION_DURATION || 1064839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration > DEFAULT_ANIMATION_DURATION) { 1065839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(invert, 0); 1066839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 1067839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return duration; 1068839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1069839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 10703d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 10713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return 0; 10723d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 10733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 1074e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy // Used for animations 1075e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"UnusedDeclaration"}) 1076839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getYProgress() { 107732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mYProgress; 107832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 107932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1080e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy // Used for animations 1081e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"UnusedDeclaration"}) 1082839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getXProgress() { 108332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mXProgress; 108432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 108544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 108644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams createOrReuseLayoutParams(View v) { 10889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); 10899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (currentLp instanceof LayoutParams) { 10909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) currentLp; 10919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setHorizontalOffset(0); 10929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setVerticalOffset(0); 1093839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 1094839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 10959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return lp; 10969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return new LayoutParams(v); 109832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 109932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 11009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen @Override 11019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1102ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen checkForAndHandleDataChanged(); 11039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final int childCount = getChildCount(); 11059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen for (int i = 0; i < childCount; i++) { 11069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final View child = getChildAt(i); 11079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childRight = mPaddingLeft + child.getMeasuredWidth(); 11099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childBottom = mPaddingTop + child.getMeasuredHeight(); 11109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) child.getLayoutParams(); 11119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, 11139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); 11149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 11169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen onLayout(); 11179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 11189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 111926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen @Override 112026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen public void advance() { 112126e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime; 1122c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen 1123c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (mAdapter == null) return; 1124ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen final int adapterCount = getCount(); 1125c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen if (adapterCount == 1 && mLoopViews) return; 1126c99ff7390d3c3fd7f5dbe773b6a824a877a1b52bAdam Cohen 112726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen if (mSwipeGestureType == GESTURE_NONE && 112826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) { 112926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen showNext(); 113026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen } 113126e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen } 113226e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen 1133839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private void measureChildren() { 1134839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int count = getChildCount(); 113536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 113636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int measuredWidth = getMeasuredWidth(); 113736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int measuredHeight = getMeasuredHeight(); 113836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 113936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childWidth = Math.round(measuredWidth*(1-PERSPECTIVE_SHIFT_FACTOR_X)) 1140026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen - mPaddingLeft - mPaddingRight; 114136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childHeight = Math.round(measuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR_Y)) 1142839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen - mPaddingTop - mPaddingBottom; 1143839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 114436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen int maxWidth = 0; 114536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen int maxHeight = 0; 114636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 1147839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen for (int i = 0; i < count; i++) { 1148839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final View child = getChildAt(i); 114936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST), 115036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST)); 115136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 115236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (child != mHighlight && child != mClickFeedback) { 115336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childMeasuredWidth = child.getMeasuredWidth(); 115436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen final int childMeasuredHeight = child.getMeasuredHeight(); 115536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (childMeasuredWidth > maxWidth) { 115636f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen maxWidth = childMeasuredWidth; 115736f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 115836f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen if (childMeasuredHeight > maxHeight) { 115936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen maxHeight = childMeasuredHeight; 116036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 116136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 116236f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 116336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 116436f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftX = PERSPECTIVE_SHIFT_FACTOR_X * measuredWidth; 116536f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftY = PERSPECTIVE_SHIFT_FACTOR_Y * measuredHeight; 116653838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen 116753838d265e36fb50341af168f5a91c3341c3cfc3Adam Cohen // If we have extra space, we try and spread the items out 116878db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (maxWidth > 0 && count > 0 && maxWidth < childWidth) { 116936f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftX = measuredWidth - maxWidth; 117036f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen } 117136f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen 117278db1aa9118edd71c2da28a2c23a0d875d1a707aAdam Cohen if (maxHeight > 0 && count > 0 && maxHeight < childHeight) { 117336f43908ae8ef9c45cbbbc6f5c16d4281da77ff7Adam Cohen mNewPerspectiveShiftY = measuredHeight - maxHeight; 1174839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1175839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1176839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1177839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen @Override 1178839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1179839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 1180839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 1181839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 1182839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 1183839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1184839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); 1185839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1186839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // We need to deal with the case where our parent hasn't told us how 1187839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // big we should be. In this case we should 1188026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float factorY = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_Y); 1189839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (heightSpecMode == MeasureSpec.UNSPECIFIED) { 1190839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen heightSpecSize = haveChildRefSize ? 1191026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen Math.round(mReferenceChildHeight * (1 + factorY)) + 1192839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingTop + mPaddingBottom : 0; 1193839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 1194189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (haveChildRefSize) { 1195026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen int height = Math.round(mReferenceChildHeight * (1 + factorY)) 1196189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn + mPaddingTop + mPaddingBottom; 1197189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (height <= heightSpecSize) { 1198189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize = height; 1199189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1200189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize |= MEASURED_STATE_TOO_SMALL; 1201ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen 1202189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1203189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1204189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize = 0; 1205189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1206839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1207839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1208026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float factorX = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_X); 1209839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (widthSpecMode == MeasureSpec.UNSPECIFIED) { 1210026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen widthSpecSize = haveChildRefSize ? 1211026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen Math.round(mReferenceChildWidth * (1 + factorX)) + 1212026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen mPaddingLeft + mPaddingRight : 0; 1213839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 1214189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (haveChildRefSize) { 1215189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight; 1216189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (width <= widthSpecSize) { 1217189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize = width; 1218189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1219189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize |= MEASURED_STATE_TOO_SMALL; 1220189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1221189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 1222189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize = 0; 1223189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 1224839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1225839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen setMeasuredDimension(widthSpecSize, heightSpecSize); 1226839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen measureChildren(); 1227839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 1228839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 12298a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov @Override 12308a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 12318a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov super.onInitializeAccessibilityEvent(event); 12328a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov event.setClassName(StackView.class.getName()); 12338a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov } 12348a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov 12358a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov @Override 12368a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 12378a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov super.onInitializeAccessibilityNodeInfo(info); 12388a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov info.setClassName(StackView.class.getName()); 123948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov info.setScrollable(getChildCount() > 1); 1240fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov if (isEnabled()) { 1241fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov if (getDisplayedChild() < getChildCount() - 1) { 1242fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 1243fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov } 1244fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov if (getDisplayedChild() > 0) { 1245fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 1246fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov } 124748d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } 124848d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } 124948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov 125048d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov @Override 125148d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov public boolean performAccessibilityAction(int action, Bundle arguments) { 125248d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov if (super.performAccessibilityAction(action, arguments)) { 125348d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov return true; 125448d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } 1255fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov if (!isEnabled()) { 1256fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov return false; 1257fb1e80a247221ee7e8f5c5deba04812021d9d07eSvetoslav Ganov } 125848d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov switch (action) { 125948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 126048d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov if (getDisplayedChild() < getChildCount() - 1) { 126148d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov showNext(); 126248d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov return true; 126348d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } 126448d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } return false; 126548d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 126648d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov if (getDisplayedChild() > 0) { 126748d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov showPrevious(); 126848d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov return true; 126948d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } 127048d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } return false; 127148d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov } 127248d1586f4065fc9ab97a679da1e4f7c327c943f2Svetoslav Ganov return false; 12738a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov } 12748a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov 12759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen class LayoutParams extends ViewGroup.LayoutParams { 12769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int horizontalOffset; 12779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int verticalOffset; 12789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View mView; 1279d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect parentRect = new Rect(); 1280d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect invalidateRect = new Rect(); 1281d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final RectF invalidateRectf = new RectF(); 1282d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect globalInvalidateRect = new Rect(); 12839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(View view) { 12859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(0, 0); 1286839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 1287839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 12889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 12899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 12909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView = view; 12919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 12929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 12939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(Context c, AttributeSet attrs) { 12949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(c, attrs); 12959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 12969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 1297839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 1298839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 1299b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 1300b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 13019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void invalidateGlobalRegion(View v, Rect r) { 1302d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen // We need to make a new rect here, so as not to modify the one passed 1303d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.set(r); 13041ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen globalInvalidateRect.union(0, 0, getWidth(), getHeight()); 13059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View p = v; 13069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!(v.getParent() != null && v.getParent() instanceof View)) return; 13079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean firstPass = true; 13099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen parentRect.set(0, 0, 0, 0); 1310b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen while (p.getParent() != null && p.getParent() instanceof View 1311d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen && !parentRect.contains(globalInvalidateRect)) { 13129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!firstPass) { 1313d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop() 1314d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen - p.getScrollY()); 13159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 13169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen firstPass = false; 13179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p = (View) p.getParent(); 1318b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen parentRect.set(p.getScrollX(), p.getScrollY(), 13191ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY()); 13201ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top, 13211ff65d105a4691594b82a7a60e0b36f2dc677d11Adam Cohen globalInvalidateRect.right, globalInvalidateRect.bottom); 13229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 13239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1324d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top, 1325d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.right, globalInvalidateRect.bottom); 1326d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen } 1327d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen 1328d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen Rect getInvalidateRect() { 1329d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen return invalidateRect; 1330d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen } 13319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1332d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen void resetInvalidateRect() { 13330ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRect.set(0, 0, 0, 0); 13349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 133532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1336a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase // This is public so that ObjectAnimator can access it 13379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setVerticalOffset(int newVerticalOffset) { 13380ac116b688380489c3690f6f65b282990c221f17Adam Cohen setOffsets(horizontalOffset, newVerticalOffset); 13399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 13409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setHorizontalOffset(int newHorizontalOffset) { 13420ac116b688380489c3690f6f65b282990c221f17Adam Cohen setOffsets(newHorizontalOffset, verticalOffset); 13430ac116b688380489c3690f6f65b282990c221f17Adam Cohen } 13440ac116b688380489c3690f6f65b282990c221f17Adam Cohen 13450ac116b688380489c3690f6f65b282990c221f17Adam Cohen public void setOffsets(int newHorizontalOffset, int newVerticalOffset) { 13460ac116b688380489c3690f6f65b282990c221f17Adam Cohen int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset; 13479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = newHorizontalOffset; 13480ac116b688380489c3690f6f65b282990c221f17Adam Cohen int verticalOffsetDelta = newVerticalOffset - verticalOffset; 13490ac116b688380489c3690f6f65b282990c221f17Adam Cohen verticalOffset = newVerticalOffset; 13509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mView != null) { 13529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.requestLayout(); 13530ac116b688380489c3690f6f65b282990c221f17Adam Cohen int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft()); 13540ac116b688380489c3690f6f65b282990c221f17Adam Cohen int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight()); 13550ac116b688380489c3690f6f65b282990c221f17Adam Cohen int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop()); 13560ac116b688380489c3690f6f65b282990c221f17Adam Cohen int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom()); 13570ac116b688380489c3690f6f65b282990c221f17Adam Cohen 13580ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRectf.set(left, top, right, bottom); 13599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xoffset = -invalidateRectf.left; 13619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yoffset = -invalidateRectf.top; 13629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(xoffset, yoffset); 13639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.getMatrix().mapRect(invalidateRectf); 13649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(-xoffset, -yoffset); 13659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13660ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRect.set((int) Math.floor(invalidateRectf.left), 13679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.floor(invalidateRectf.top), 13689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.right), 13699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.bottom)); 13709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 13719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateGlobalRegion(mView, invalidateRect); 13729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 13739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 137432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 137532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 13769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static class HolographicHelper { 13779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mHolographicPaint = new Paint(); 13789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mErasePaint = new Paint(); 1379839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private final Paint mBlurPaint = new Paint(); 13808baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private static final int RES_OUT = 0; 13818baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private static final int CLICK_FEEDBACK = 1; 13828baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private float mDensity; 1383e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private BlurMaskFilter mSmallBlurMaskFilter; 1384e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private BlurMaskFilter mLargeBlurMaskFilter; 1385e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Canvas mCanvas = new Canvas(); 1386e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Canvas mMaskCanvas = new Canvas(); 1387e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final int[] mTmpXY = new int[2]; 1388e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Matrix mIdentityMatrix = new Matrix(); 138932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1390dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen HolographicHelper(Context context) { 13918baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mDensity = context.getResources().getDisplayMetrics().density; 1392dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 13939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHolographicPaint.setFilterBitmap(true); 1394839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); 13959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 13969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setFilterBitmap(true); 1397e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy 1398e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL); 1399e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL); 14009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 14019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 140226f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen Bitmap createClickOutline(View v, int color) { 140326f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen return createOutline(v, CLICK_FEEDBACK, color); 140426f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen } 140526f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen 140626f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen Bitmap createResOutline(View v, int color) { 140726f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen return createOutline(v, RES_OUT, color); 14088baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 14098baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 141026f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen Bitmap createOutline(View v, int type, int color) { 141126f072c3ee4f2baecf4fd3f8ed829ed709055cf4Adam Cohen mHolographicPaint.setColor(color); 14128baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (type == RES_OUT) { 1413e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mBlurPaint.setMaskFilter(mSmallBlurMaskFilter); 14148baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } else if (type == CLICK_FEEDBACK) { 1415e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mBlurPaint.setMaskFilter(mLargeBlurMaskFilter); 14168baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 14178baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 14189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) { 14199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return null; 14209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 14219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1422dde331cebd87982faded6818ad5f9927ff994c96Dianne Hackborn Bitmap bitmap = Bitmap.createBitmap(v.getResources().getDisplayMetrics(), 1423dde331cebd87982faded6818ad5f9927ff994c96Dianne Hackborn v.getMeasuredWidth(), v.getMeasuredHeight(), Bitmap.Config.ARGB_8888); 1424e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mCanvas.setBitmap(bitmap); 142532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 14269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float rotationX = v.getRotationX(); 1427839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float rotation = v.getRotation(); 1428839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float translationY = v.getTranslationY(); 1429026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen float translationX = v.getTranslationX(); 14309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(0); 1431839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(0); 1432839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(0); 1433026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen v.setTranslationX(0); 1434e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy v.draw(mCanvas); 14359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(rotationX); 1436839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(rotation); 1437839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(translationY); 1438026e121bc35b9012943d8ea86518b51270d0c0efAdam Cohen v.setTranslationX(translationX); 14399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1440e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy drawOutline(mCanvas, bitmap); 14416311d0a079702b29984c0d31937345be105e1a5eDianne Hackborn mCanvas.setBitmap(null); 14429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return bitmap; 14439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 14449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 14459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void drawOutline(Canvas dest, Bitmap src) { 1446e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy final int[] xy = mTmpXY; 1447839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Bitmap mask = src.extractAlpha(mBlurPaint, xy); 1448e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mMaskCanvas.setBitmap(mask); 1449e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint); 14509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.drawColor(0, PorterDuff.Mode.CLEAR); 1451e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy dest.setMatrix(mIdentityMatrix); 1452839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint); 14536311d0a079702b29984c0d31937345be105e1a5eDianne Hackborn mMaskCanvas.setBitmap(null); 14549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mask.recycle(); 14559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 145632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 1457ef17dd497edc14ca753616862efaa3457e1df5daAdam Cohen} 1458