StackView.java revision 26e30bb7fe373ad4bb569a5de2577e0c857e7c27
144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/* 244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Copyright (C) 2010 The Android Open Source Project 344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * 444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Licensed under the Apache License, Version 2.0 (the "License"); 544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * you may not use this file except in compliance with the License. 644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * You may obtain a copy of the License at 744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * 844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * http://www.apache.org/licenses/LICENSE-2.0 944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * 1044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Unless required by applicable law or agreed to in writing, software 1144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * distributed under the License is distributed on an "AS IS" BASIS, 1244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * See the License for the specific language governing permissions and 1444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * limitations under the License. 1544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 1644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpackage android.widget; 1844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 19a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator; 20e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guyimport android.animation.PropertyValuesHolder; 2144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context; 2232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap; 23839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter; 2432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas; 2532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix; 2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint; 2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff; 2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode; 2944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect; 309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF; 31d51bbb5b56446519db88f49f2682da782b0069acAdam Cohenimport android.graphics.Region; 32839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter; 3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet; 3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log; 3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent; 3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker; 3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View; 3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration; 3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup; 40b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator; 4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView; 4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView 4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/** 4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe 4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children. 4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator { 4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final String TAG = "StackView"; 5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Default animation parameters 5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 54e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy private static final int DEFAULT_ANIMATION_DURATION = 400; 55e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy private static final int MINIMUM_ANIMATION_DURATION = 50; 5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 58839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Parameters effecting the perspective visuals 59839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 60839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f; 61e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"FieldCanBeLocal"}) 62839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static float PERSPECTIVE_SCALE_FACTOR = 0.35f; 63839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 64839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 65839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Represent the two possible stack modes, one where items slide up, and the other 66839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * where items slide down. The perspective is also inverted between these two modes. 67839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 68839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_UP = 0; 69839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_DOWN = 1; 70839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 71839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 7244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These specify the different gesture states 7344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 745b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_NONE = 0; 755b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_UP = 1; 765b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_DOWN = 2; 7744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 7844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 7944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Specifies how far you need to swipe (up or down) before it 8044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * will be consider a completed gesture when you lift your finger 8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 82a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen private static final float SWIPE_THRESHOLD_RATIO = 0.2f; 83a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen 84a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen /** 85a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen * Specifies the total distance, relative to the size of the stack, 86a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen * that views will be slid, either up or down 87a9238c89a43500ed0bcdeaee182be08ff991c627Adam Cohen */ 885b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SLIDE_UP_RATIO = 0.7f; 8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Sentinel value for no current active pointer. 9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Used by {@link #mActivePointerId}. 9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 9444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private static final int INVALID_POINTER = -1; 9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 97839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Number of active views in the stack. One fewer view is actually visible, as one is hidden. 98839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 99839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int NUM_ACTIVE_VIEWS = 5; 100839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 101dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private static final int FRAME_PADDING = 4; 102839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 103e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Rect mTouchRect = new Rect(); 104e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy 10526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000; 10626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen 107839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These variables are all related to the current state of touch interaction 10944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * with the stack 11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialY; 11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialX; 11344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mActivePointerId; 11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mYVelocity = 0; 11544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeGestureType = GESTURE_NONE; 116dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mSlideAmount; 11744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeThreshold; 11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mTouchSlop; 11944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mMaximumVelocity; 12044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private VelocityTracker mVelocityTracker; 1213352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen private boolean mTransitionIsSetup = false; 12244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static HolographicHelper sHolographicHelper; 12432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private ImageView mHighlight; 1258baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private ImageView mClickFeedback; 1268baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private boolean mClickFeedbackIsValid = false; 12732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private StackSlider mStackSlider; 12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mFirstLayoutHappened = false; 12926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen private long mLastInteractionTime = 0; 130839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private int mStackMode; 131dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mFramePadding; 1320ac116b688380489c3690f6f65b282990c221f17Adam Cohen private final Rect stackInvalidateRect = new Rect(); 13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context) { 13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context); 13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context, AttributeSet attrs) { 14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context, attrs); 14144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 14244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void initStackView() { 14596d8d56302da81b24333b204e6d7f15064538036Adam Cohen configureViewAnimator(NUM_ACTIVE_VIEWS, 1); 14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setStaticTransformationsEnabled(true); 14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 148b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mTouchSlop = configuration.getScaledTouchSlop(); 14944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 15132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 15232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight = new ImageView(getContext()); 15332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.setLayoutParams(new LayoutParams(mHighlight)); 15432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight)); 1558baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 1568baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback = new ImageView(getContext()); 1578baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback)); 1588baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback)); 1598baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(INVISIBLE); 1608baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 16132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider = new StackSlider(); 16232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (sHolographicHelper == null) { 164dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen sHolographicHelper = new HolographicHelper(mContext); 16532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 1669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipChildren(false); 1679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipToPadding(false); 1681480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen 169839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // This sets the form of the StackView, which is currently to have the perspective-shifted 170839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // views above the active view, and have items slide down when sliding out. The opposite is 171839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // available by using ITEMS_SLIDE_UP. 172839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mStackMode = ITEMS_SLIDE_DOWN; 173839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1741480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // This is a flag to indicate the the stack is loading for the first time 1751480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen mWhichChild = -1; 176dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 177dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // Adjust the frame padding based on the density, since the highlight changes based 178dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // on the density 179dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen final float density = mContext.getResources().getDisplayMetrics().density; 180dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mFramePadding = (int) Math.ceil(density * FRAME_PADDING); 18144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 18244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 18444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Animate the views between different relative indexes within the {@link AdapterViewAnimator} 18544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 18644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void animateViewForTransition(int fromIndex, int toIndex, View view) { 18796d8d56302da81b24333b204e6d7f15064538036Adam Cohen if (fromIndex == -1 && toIndex != 0) { 18844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item in 18944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (view.getAlpha() == 1) { 19044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0); 19144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 192b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(VISIBLE); 193b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 1942794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f); 1952794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase fadeIn.setDuration(DEFAULT_ANIMATION_DURATION); 19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeIn.start(); 19796d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (fromIndex == 0 && toIndex == 1) { 19844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item in 19944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setVisibility(VISIBLE); 20032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 201839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity)); 20244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 203b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 2042794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f); 2052794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 2062794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 207839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen slideInX, slideInY); 2082794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 209839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 210839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 21196d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (fromIndex == 1 && toIndex == 0) { 21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item out 213839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity)); 21444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 215b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 2162794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f); 2172794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 2182794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 2192794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase slideOutX, slideOutY); 2202794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 221839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 222839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 22396d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (fromIndex == -1 && toIndex == 0) { 22444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Make sure this view that is "waiting in the wings" is invisible 22544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0.0f); 226b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(INVISIBLE); 227b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 228dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen lp.setVerticalOffset(-mSlideAmount); 22944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (toIndex == -1) { 23044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item out 2312794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f); 2322794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase fadeOut.setDuration(DEFAULT_ANIMATION_DURATION); 23344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeOut.start(); 23444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 235839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 236839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // Implement the faked perspective 237839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (toIndex != -1) { 238f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen transformViewAtIndex(toIndex, view); 239f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 240f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 241839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 2423352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen private void setupStackSlider(View v, int mode) { 2433352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setMode(mode); 2443352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 2453352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setImageBitmap(sHolographicHelper.createOutline(v)); 2463352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setRotation(v.getRotation()); 2473352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.setTranslationY(v.getTranslationY()); 2483352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mHighlight.bringToFront(); 2493352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen v.bringToFront(); 2503352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setView(v); 2513352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 2523352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen v.setVisibility(VISIBLE); 2533352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2543352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2553352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 2563352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @Override 2573352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @android.view.RemotableViewMethod 2583352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen public void showNext() { 2590ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (mSwipeGestureType != GESTURE_NONE) return; 2603352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (!mTransitionIsSetup) { 2613352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen View v = getViewAtRelativeIndex(1); 2623352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 2633352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, StackSlider.NORMAL_MODE); 2643352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setYProgress(0); 2653352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setXProgress(0); 2663352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2673352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2683352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen super.showNext(); 2693352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2703352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 2713352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @Override 2723352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen @android.view.RemotableViewMethod 2733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen public void showPrevious() { 2740ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (mSwipeGestureType != GESTURE_NONE) return; 2753352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (!mTransitionIsSetup) { 2763352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen View v = getViewAtRelativeIndex(0); 2773352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen if (v != null) { 2783352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, StackSlider.NORMAL_MODE); 2793352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setYProgress(1); 2803352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mStackSlider.setXProgress(0); 2813352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2823352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2833352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen super.showPrevious(); 2843352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen } 2853352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 286f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen private void transformViewAtIndex(int index, View view) { 287189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn float maxPerpectiveShift = getMeasuredHeight() * PERSPECTIVE_SHIFT_FACTOR; 288839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 28996d8d56302da81b24333b204e6d7f15064538036Adam Cohen index = mMaxNumActiveViews - index - 1; 29096d8d56302da81b24333b204e6d7f15064538036Adam Cohen if (index == mMaxNumActiveViews - 1) index--; 291839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 29296d8d56302da81b24333b204e6d7f15064538036Adam Cohen float r = (index * 1.0f) / (mMaxNumActiveViews - 2); 293839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 294f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r); 2952794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", scale); 2962794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", scale); 297839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 298f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen r = (float) Math.pow(r, 2); 299839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 300f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; 301dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float perspectiveTranslation = -stackDirection * r * maxPerpectiveShift; 302dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float scaleShiftCorrection = stackDirection * (1 - scale) * 303189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR) / 2.0f); 304dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float transY = perspectiveTranslation + scaleShiftCorrection; 305f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen 3062794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY); 3072794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY, translationY); 3082794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(100); 309f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen pa.start(); 310f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 311f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen 31296d8d56302da81b24333b204e6d7f15064538036Adam Cohen @Override 31396d8d56302da81b24333b204e6d7f15064538036Adam Cohen void showOnly(int childIndex, boolean animate, boolean onLayout) { 31496d8d56302da81b24333b204e6d7f15064538036Adam Cohen super.showOnly(childIndex, animate, onLayout); 31596d8d56302da81b24333b204e6d7f15064538036Adam Cohen 31696d8d56302da81b24333b204e6d7f15064538036Adam Cohen // Here we need to make sure that the z-order of the children is correct 3173352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) { 31896d8d56302da81b24333b204e6d7f15064538036Adam Cohen int index = modulo(i, getWindowSize()); 3190ac116b688380489c3690f6f65b282990c221f17Adam Cohen ViewAndIndex vi = mViewsMap.get(index); 3200ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (vi != null) { 3210ac116b688380489c3690f6f65b282990c221f17Adam Cohen View v = mViewsMap.get(index).view; 3220ac116b688380489c3690f6f65b282990c221f17Adam Cohen if (v != null) v.bringToFront(); 3230ac116b688380489c3690f6f65b282990c221f17Adam Cohen } 32496d8d56302da81b24333b204e6d7f15064538036Adam Cohen } 3253352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mTransitionIsSetup = false; 3268baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedbackIsValid = false; 3278baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 3288baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 3298baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void updateClickFeedback() { 3308baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (!mClickFeedbackIsValid) { 3319c295482dd739e80dd49ea0dd3102ad6be6742ddAdam Cohen View v = getViewAtRelativeIndex(1); 3328baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (v != null) { 3338baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v, 3348baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen HolographicHelper.CLICK_FEEDBACK)); 3358baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setTranslationX(v.getTranslationX()); 3368baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setTranslationY(v.getTranslationY()); 3378baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 3388baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedbackIsValid = true; 3398baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 3408baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 3418baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 3428baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen @Override 3438baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void showTapFeedback(View v) { 3448baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen updateClickFeedback(); 3458baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(VISIBLE); 3468baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.bringToFront(); 3478baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen invalidate(); 3488baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 3498baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 3508baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen @Override 3518baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen void hideTapFeedback(View v) { 3528baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mClickFeedback.setVisibility(INVISIBLE); 3538baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen invalidate(); 35496d8d56302da81b24333b204e6d7f15064538036Adam Cohen } 35596d8d56302da81b24333b204e6d7f15064538036Adam Cohen 356f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen private void updateChildTransforms() { 35796d8d56302da81b24333b204e6d7f15064538036Adam Cohen for (int i = 0; i < getNumActiveViews(); i++) { 358f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen View v = getViewAtRelativeIndex(i); 359f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen if (v != null) { 360f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen transformViewAtIndex(i, v); 361f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 362839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 36344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 365dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen @Override 366dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen FrameLayout getFrameForChild() { 367dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen FrameLayout fl = new FrameLayout(mContext); 368dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding); 369dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen return fl; 370dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen } 371dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 37244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 37344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Apply any necessary tranforms for the child that is being added. 37444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 37544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void applyTransformForChildAtIndex(View child, int relativeIndex) { 37644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 37744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 37844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 3799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void dispatchDraw(Canvas canvas) { 3800ac116b688380489c3690f6f65b282990c221f17Adam Cohen canvas.getClipBounds(stackInvalidateRect); 381d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen final int childCount = getChildCount(); 382d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen for (int i = 0; i < childCount; i++) { 383d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); 3840ac116b688380489c3690f6f65b282990c221f17Adam Cohen stackInvalidateRect.union(lp.getInvalidateRect()); 385d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen lp.resetInvalidateRect(); 38644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 387d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen canvas.save(Canvas.CLIP_SAVE_FLAG); 3880ac116b688380489c3690f6f65b282990c221f17Adam Cohen canvas.clipRect(stackInvalidateRect, Region.Op.UNION); 389d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen super.dispatchDraw(canvas); 390d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen canvas.restore(); 3919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 3939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private void onLayout() { 39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mFirstLayoutHappened) { 395dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight()); 396f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen updateChildTransforms(); 397dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount); 39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mFirstLayoutHappened = true; 39944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 40244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 40344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onInterceptTouchEvent(MotionEvent ev) { 40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 40544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch(action & MotionEvent.ACTION_MASK) { 40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_DOWN: { 40744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mActivePointerId == INVALID_POINTER) { 40844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX = ev.getX(); 40944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY = ev.getY(); 41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(0); 41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 41344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 41544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 41644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 41744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 41844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 41944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 42044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 42244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 42344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 42432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 42544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 42644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 42944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 43044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: 43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 43344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 43444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 43844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return mSwipeGestureType != GESTURE_NONE; 43944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 44044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 44132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private void beginGestureIfNeeded(float deltaY) { 44232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 4433d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 44432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen cancelLongPress(); 44532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen requestDisallowInterceptTouchEvent(true); 44632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 447d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen if (mAdapter == null) return; 448d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen 449839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int activeIndex; 450839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 45196d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1; 452839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 45396d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0; 454839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 45532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 4563352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen int stackMode; 4571b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen if (mLoopViews) { 4583352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.NORMAL_MODE; 45996d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == -1) { 46096d8d56302da81b24333b204e6d7f15064538036Adam Cohen activeIndex++; 4613352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.BEGINNING_OF_STACK_MODE; 46296d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount() - 1) { 4633352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.END_OF_STACK_MODE; 4643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else { 4653352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen stackMode = StackSlider.NORMAL_MODE; 46632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 4673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 4683352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE; 4693352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen 4703d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen View v = getViewAtRelativeIndex(activeIndex); 4713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (v == null) return; 4723d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 4733352b6807f9b728b91cceb3ea5f2a065ace95da3Adam Cohen setupStackSlider(v, stackMode); 4743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 4753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We only register this gesture if we've made it this far without a problem 4763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mSwipeGestureType = swipeGestureType; 477a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen cancelHandleClick(); 47832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 47932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 48032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 48144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 48244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onTouchEvent(MotionEvent ev) { 483a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen super.onTouchEvent(ev); 484a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20Adam Cohen 48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 48844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 48944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 49044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 49144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 49244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 49344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 49432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float newX = ev.getX(pointerIndex); 49544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 49632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float deltaX = newX - mInitialX; 49744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker == null) { 49844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = VelocityTracker.obtain(); 49944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 50044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.addMovement(ev); 50144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 50244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch (action & MotionEvent.ACTION_MASK) { 50344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 50432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 50532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 506dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float rx = deltaX / (mSlideAmount * 1.0f); 50732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 508dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 509839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 51032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(1 - r); 51132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 51232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 51332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 514dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 515839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 51632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(r); 51732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 51832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 51944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 52044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 52144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 52244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: { 52344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 52444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 52544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 52644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 52744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 52844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 52944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 53044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 53144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 53244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 53344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 53444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 53544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 53644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 53744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 53844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 53944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void onSecondaryPointerUp(MotionEvent ev) { 54044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int activePointerIndex = ev.getActionIndex(); 54144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int pointerId = ev.getPointerId(activePointerIndex); 54244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerId == mActivePointerId) { 54344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 54496d8d56302da81b24333b204e6d7f15064538036Adam Cohen int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1; 54544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 54644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(activeViewIndex); 54744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v == null) return; 54844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 54944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Our primary pointer has gone up -- let's see if we can find 55044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // another pointer on the view. If so, then we should replace 55144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // our primary pointer with this new pointer and adjust things 55244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so that the view doesn't jump 55344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (int index = 0; index < ev.getPointerCount(); index++) { 55444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (index != activePointerIndex) { 55544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 55644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float x = ev.getX(index); 55744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float y = ev.getY(index); 55844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 559e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 560e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy if (mTouchRect.contains(Math.round(x), Math.round(y))) { 56144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldX = ev.getX(activePointerIndex); 56244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldY = ev.getY(activePointerIndex); 56344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 56444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // adjust our frame of reference to avoid a jump 56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY += (y - oldY); 56644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX += (x - oldX); 56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 56844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(index); 56944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 57044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.clear(); 57144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 57244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ok, we're good, we found a new pointer which is touching the active view 57344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return; 57444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 57544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 57644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 57744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if we made it this far, it means we didn't find a satisfactory new pointer :(, 5783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so end the gesture 57944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 58044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 58144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 58244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 58344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void handlePointerUp(MotionEvent ev) { 58444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 58544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 58644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int deltaY = (int) (newY - mInitialY); 58726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen mLastInteractionTime = System.currentTimeMillis(); 58844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mVelocityTracker != null) { 5903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 5913d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); 5923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 59344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 59444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 59544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.recycle(); 59644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = null; 59744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 59844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN 6003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 6010ac116b688380489c3690f6f65b282990c221f17Adam Cohen // We reset the gesture variable, because otherwise we will ignore showPrevious() / 6020ac116b688380489c3690f6f65b282990c221f17Adam Cohen // showNext(); 6030ac116b688380489c3690f6f65b282990c221f17Adam Cohen mSwipeGestureType = GESTURE_NONE; 6040ac116b688380489c3690f6f65b282990c221f17Adam Cohen 60544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe down 606839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 607839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showPrevious(); 60896d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else { 60996d8d56302da81b24333b204e6d7f15064538036Adam Cohen showNext(); 610839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 61132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 6123d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP 6133d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 6140ac116b688380489c3690f6f65b282990c221f17Adam Cohen // We reset the gesture variable, because otherwise we will ignore showPrevious() / 6150ac116b688380489c3690f6f65b282990c221f17Adam Cohen // showNext(); 6160ac116b688380489c3690f6f65b282990c221f17Adam Cohen mSwipeGestureType = GESTURE_NONE; 6170ac116b688380489c3690f6f65b282990c221f17Adam Cohen 61844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe up 619839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 620839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showNext(); 62196d8d56302da81b24333b204e6d7f15064538036Adam Cohen } else { 62296d8d56302da81b24333b204e6d7f15064538036Adam Cohen showPrevious(); 623839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 624839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 62532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 626839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) { 62744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe up far enough, snap back down 628839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 629839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0; 630839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 631839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 632839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 633839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 634839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 63532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 636b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 6372794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress); 6382794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 6392794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 640839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 6412794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 642839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 643839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 64432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 64544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe down far enough, snap back up 646839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1; 647839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 648839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 649839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 650839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 651839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 652839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 6533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 654b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 6552794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = 6562794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder.ofFloat("YProgress",finalYProgress); 6572794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 6582794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 659839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 6602794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 661839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 66244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 66344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 66444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 66544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 66632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 66732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 66832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private class StackSlider { 66932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen View mView; 67032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mYProgress; 67132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mXProgress; 67232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int NORMAL_MODE = 0; 6743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int BEGINNING_OF_STACK_MODE = 1; 6753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int END_OF_STACK_MODE = 2; 6763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int mMode = NORMAL_MODE; 6783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 679b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider() { 680b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 681b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 682b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider(StackSlider copy) { 683b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mView = copy.mView; 684b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mYProgress = copy.mYProgress; 685b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mXProgress = copy.mXProgress; 6863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = copy.mMode; 687b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 688b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 68932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float cubic(float r) { 690839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f; 69132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 69232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 69332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float highlightAlphaInterpolator(float r) { 69432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.4f; 69532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r < pivot) { 696839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(r / pivot); 69732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 698839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(1 - (r - pivot) / (1 - pivot)); 69932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 70032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 70132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 70232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float viewAlphaInterpolator(float r) { 70332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.3f; 70432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r > pivot) { 705839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 70632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 70732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0; 70832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 70932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 71032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 711b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen private float rotationInterpolator(float r) { 712b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen float pivot = 0.2f; 713b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen if (r < pivot) { 714b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen return 0; 715b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } else { 716839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 717b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 718b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 719b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 72032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen void setView(View v) { 72132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mView = v; 72232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 72332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 72432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setYProgress(float r) { 72532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 72632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.min(1.0f, r); 72732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.max(0, r); 72832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 72932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mYProgress = r; 730a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen if (mView == null) return; 731a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen 73232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 73332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 73432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 735839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; 736839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 7373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen switch (mMode) { 7383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case NORMAL_MODE: 739dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 740dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 7413d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 7423d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 743839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float alpha = viewAlphaInterpolator(1 - r); 7443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 7453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We make sure that views which can't be seen (have 0 alpha) are also invisible 7463d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so that they don't interfere with click events. 7473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) { 7483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(VISIBLE); 7493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (alpha == 0 && mView.getAlpha() != 0 7503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mView.getVisibility() == VISIBLE) { 7513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(INVISIBLE); 7523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 753b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 7543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setAlpha(alpha); 755839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 756839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 7573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 75896d8d56302da81b24333b204e6d7f15064538036Adam Cohen case END_OF_STACK_MODE: 759839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = r * 0.2f; 760dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 761dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 7623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 7633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 76496d8d56302da81b24333b204e6d7f15064538036Adam Cohen case BEGINNING_OF_STACK_MODE: 765839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = (1-r) * 0.2f; 766dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 767dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 7683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 7693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 770b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 77132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 77232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 77332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setXProgress(float r) { 77432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 7753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.min(2.0f, r); 7763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.max(-2.0f, r); 77732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 77832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mXProgress = r; 77932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 780a02fdf1ba03fad71cc80a89dfc74b17456d5b4a5Adam Cohen if (mView == null) return; 78132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 78232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 78332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r *= 0.2f; 785dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 786dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 78732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 78832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen void setMode(int mode) { 7903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = mode; 7913d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 7923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 7933d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForNeutralPosition() { 794839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, 0); 7953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 7963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 7973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForOffscreenPosition() { 798839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, 0); 799839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 800839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 801839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForNeutralPosition(float velocity) { 802839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, velocity); 803839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 804839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 805839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForOffscreenPosition(float velocity) { 806839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, velocity); 8073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 8083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 809839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private float getDuration(boolean invert, float velocity) { 8103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView != null) { 8113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 8123d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 813839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) + 814839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Math.pow(viewLp.verticalOffset, 2)); 815dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) + 816dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen Math.pow(0.4f * mSlideAmount, 2)); 817839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 818839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (velocity == 0) { 819839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION; 820839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 821839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float duration = invert ? d / Math.abs(velocity) : 822839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen (maxd - d) / Math.abs(velocity); 823839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (duration < MINIMUM_ANIMATION_DURATION || 824839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration > DEFAULT_ANIMATION_DURATION) { 825839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(invert, 0); 826839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 827839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return duration; 828839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 829839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 8303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 8313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return 0; 8323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 8333d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 834e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy // Used for animations 835e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"UnusedDeclaration"}) 836839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getYProgress() { 83732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mYProgress; 83832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 83932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 840e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy // Used for animations 841e5ebcb0107a939395e03592fd44c746cd09e311dRomain Guy @SuppressWarnings({"UnusedDeclaration"}) 842839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getXProgress() { 84332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mXProgress; 84432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 84544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 84644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 84744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 84844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public void onRemoteAdapterConnected() { 84944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super.onRemoteAdapterConnected(); 8501480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // On first run, we want to set the stack to the end. 85196d8d56302da81b24333b204e6d7f15064538036Adam Cohen if (mWhichChild == -1) { 85296d8d56302da81b24333b204e6d7f15064538036Adam Cohen mWhichChild = 0; 8533042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen } 85496d8d56302da81b24333b204e6d7f15064538036Adam Cohen setDisplayedChild(mWhichChild); 85544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 85632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 8579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams createOrReuseLayoutParams(View v) { 8589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); 8599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (currentLp instanceof LayoutParams) { 8609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) currentLp; 8619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setHorizontalOffset(0); 8629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setVerticalOffset(0); 863839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 864839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 8659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return lp; 8669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return new LayoutParams(v); 86832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 86932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 8709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen @Override 8719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 8729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean dataChanged = mDataChanged; 8739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (dataChanged) { 8749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen handleDataChanged(); 8759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // if the data changes, mWhichChild might be out of the bounds of the adapter 8779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // in this case, we reset mWhichChild to the beginning 8789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mWhichChild >= mAdapter.getCount()) 8799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mWhichChild = 0; 8809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen showOnly(mWhichChild, true, true); 8826364f2bbe5254b4274f3feffc48f4259eacc205eWinson Chung refreshChildren(); 8839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final int childCount = getChildCount(); 8869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen for (int i = 0; i < childCount; i++) { 8879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final View child = getChildAt(i); 8889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childRight = mPaddingLeft + child.getMeasuredWidth(); 8909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childBottom = mPaddingTop + child.getMeasuredHeight(); 8919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) child.getLayoutParams(); 8929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, 8949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); 8959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mDataChanged = false; 8999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen onLayout(); 9009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 90226e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen @Override 90326e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen public void advance() { 90426e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime; 90526e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen if (mSwipeGestureType == GESTURE_NONE && 90626e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) { 90726e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen showNext(); 90826e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen } 90926e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen } 91026e30bb7fe373ad4bb569a5de2577e0c857e7c27Adam Cohen 911839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private void measureChildren() { 912839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int count = getChildCount(); 913189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn final int childWidth = getMeasuredWidth() - mPaddingLeft - mPaddingRight; 914189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn final int childHeight = Math.round(getMeasuredHeight()*(1-PERSPECTIVE_SHIFT_FACTOR)) 915839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen - mPaddingTop - mPaddingBottom; 916839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 917839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen for (int i = 0; i < count; i++) { 918839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final View child = getChildAt(i); 919839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 920839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); 921839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 922839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 923839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 924839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen @Override 925839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 926839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 927839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 928839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 929839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 930839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 931839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); 932839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 933839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // We need to deal with the case where our parent hasn't told us how 934839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // big we should be. In this case we should 935839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR); 936839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (heightSpecMode == MeasureSpec.UNSPECIFIED) { 937839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen heightSpecSize = haveChildRefSize ? 938839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Math.round(mReferenceChildHeight * (1 + factor)) + 939839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingTop + mPaddingBottom : 0; 940839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 941189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (haveChildRefSize) { 942189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn int height = Math.round(mReferenceChildHeight * (1 + factor)) 943189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn + mPaddingTop + mPaddingBottom; 944189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (height <= heightSpecSize) { 945189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize = height; 946189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 947189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize |= MEASURED_STATE_TOO_SMALL; 948189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 949189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 950189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn heightSpecSize = 0; 951189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 952839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 953839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 954839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (widthSpecMode == MeasureSpec.UNSPECIFIED) { 955839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + 956839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingRight : 0; 957839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 958189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (haveChildRefSize) { 959189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight; 960189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn if (width <= widthSpecSize) { 961189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize = width; 962189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 963189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize |= MEASURED_STATE_TOO_SMALL; 964189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 965189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } else { 966189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn widthSpecSize = 0; 967189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn } 968839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 969839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 970839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen setMeasuredDimension(widthSpecSize, heightSpecSize); 971839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen measureChildren(); 972839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 973839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 9749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen class LayoutParams extends ViewGroup.LayoutParams { 9759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int horizontalOffset; 9769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int verticalOffset; 9779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View mView; 978d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect parentRect = new Rect(); 979d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect invalidateRect = new Rect(); 980d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final RectF invalidateRectf = new RectF(); 981d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen private final Rect globalInvalidateRect = new Rect(); 9829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(View view) { 9849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(0, 0); 985839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 986839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 9879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 9889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 9899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView = view; 9909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(Context c, AttributeSet attrs) { 9939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(c, attrs); 9949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 9959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 996839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 997839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 998b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 999b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 10009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void invalidateGlobalRegion(View v, Rect r) { 1001d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen // We need to make a new rect here, so as not to modify the one passed 1002d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.set(r); 10039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View p = v; 10049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!(v.getParent() != null && v.getParent() instanceof View)) return; 10059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean firstPass = true; 10079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen parentRect.set(0, 0, 0, 0); 10089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int depth = 0; 1009b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen while (p.getParent() != null && p.getParent() instanceof View 1010d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen && !parentRect.contains(globalInvalidateRect)) { 10119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!firstPass) { 1012d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop() 1013d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen - p.getScrollY()); 10149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen depth++; 10159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen firstPass = false; 10179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p = (View) p.getParent(); 1018b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen parentRect.set(p.getScrollX(), p.getScrollY(), 1019b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY()); 1020839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 10219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1023d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top, 1024d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen globalInvalidateRect.right, globalInvalidateRect.bottom); 1025d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen } 1026d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen 1027d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen Rect getInvalidateRect() { 1028d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen return invalidateRect; 1029d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen } 10309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1031d51bbb5b56446519db88f49f2682da782b0069acAdam Cohen void resetInvalidateRect() { 10320ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRect.set(0, 0, 0, 0); 10339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 103432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1035a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase // This is public so that ObjectAnimator can access it 10369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setVerticalOffset(int newVerticalOffset) { 10370ac116b688380489c3690f6f65b282990c221f17Adam Cohen setOffsets(horizontalOffset, newVerticalOffset); 10389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setHorizontalOffset(int newHorizontalOffset) { 10410ac116b688380489c3690f6f65b282990c221f17Adam Cohen setOffsets(newHorizontalOffset, verticalOffset); 10420ac116b688380489c3690f6f65b282990c221f17Adam Cohen } 10430ac116b688380489c3690f6f65b282990c221f17Adam Cohen 10440ac116b688380489c3690f6f65b282990c221f17Adam Cohen public void setOffsets(int newHorizontalOffset, int newVerticalOffset) { 10450ac116b688380489c3690f6f65b282990c221f17Adam Cohen int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset; 10469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = newHorizontalOffset; 10470ac116b688380489c3690f6f65b282990c221f17Adam Cohen int verticalOffsetDelta = newVerticalOffset - verticalOffset; 10480ac116b688380489c3690f6f65b282990c221f17Adam Cohen verticalOffset = newVerticalOffset; 10499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mView != null) { 10519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.requestLayout(); 10520ac116b688380489c3690f6f65b282990c221f17Adam Cohen int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft()); 10530ac116b688380489c3690f6f65b282990c221f17Adam Cohen int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight()); 10540ac116b688380489c3690f6f65b282990c221f17Adam Cohen int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop()); 10550ac116b688380489c3690f6f65b282990c221f17Adam Cohen int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom()); 10560ac116b688380489c3690f6f65b282990c221f17Adam Cohen 10570ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRectf.set(left, top, right, bottom); 10589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xoffset = -invalidateRectf.left; 10609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yoffset = -invalidateRectf.top; 10619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(xoffset, yoffset); 10629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.getMatrix().mapRect(invalidateRectf); 10639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(-xoffset, -yoffset); 10649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10650ac116b688380489c3690f6f65b282990c221f17Adam Cohen invalidateRect.set((int) Math.floor(invalidateRectf.left), 10669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.floor(invalidateRectf.top), 10679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.right), 10689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.bottom)); 10699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 10709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateGlobalRegion(mView, invalidateRect); 10719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 10729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 107332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 107432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 10759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static class HolographicHelper { 10769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mHolographicPaint = new Paint(); 10779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mErasePaint = new Paint(); 1078839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private final Paint mBlurPaint = new Paint(); 10798baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private static final int RES_OUT = 0; 10808baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private static final int CLICK_FEEDBACK = 1; 10818baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen private float mDensity; 1082e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private BlurMaskFilter mSmallBlurMaskFilter; 1083e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private BlurMaskFilter mLargeBlurMaskFilter; 1084e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Canvas mCanvas = new Canvas(); 1085e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Canvas mMaskCanvas = new Canvas(); 1086e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final int[] mTmpXY = new int[2]; 1087e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy private final Matrix mIdentityMatrix = new Matrix(); 108832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1089dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen HolographicHelper(Context context) { 10908baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mDensity = context.getResources().getDisplayMetrics().density; 1091dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 10929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHolographicPaint.setFilterBitmap(true); 1093839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); 10949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 10959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setFilterBitmap(true); 1096e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy 1097e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL); 1098e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL); 10999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 11009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap createOutline(View v) { 11028baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen return createOutline(v, RES_OUT); 11038baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 11048baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 11058baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen Bitmap createOutline(View v, int type) { 11068baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen if (type == RES_OUT) { 11078baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mHolographicPaint.setColor(0xff6699ff); 1108e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mBlurPaint.setMaskFilter(mSmallBlurMaskFilter); 11098baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } else if (type == CLICK_FEEDBACK) { 11108baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen mHolographicPaint.setColor(0x886699ff); 1111e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mBlurPaint.setMaskFilter(mLargeBlurMaskFilter); 11128baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen } 11138baf5df0fa5b3453a7f17e95746c5d8cadc00163Adam Cohen 11149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) { 11159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return null; 11169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 11179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), 11199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap.Config.ARGB_8888); 1120e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mCanvas.setBitmap(bitmap); 112132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 11229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float rotationX = v.getRotationX(); 1123839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float rotation = v.getRotation(); 1124839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float translationY = v.getTranslationY(); 11259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(0); 1126839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(0); 1127839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(0); 1128e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy v.draw(mCanvas); 11299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(rotationX); 1130839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(rotation); 1131839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(translationY); 11329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 1133e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy drawOutline(mCanvas, bitmap); 11349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return bitmap; 11359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 11369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 11379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void drawOutline(Canvas dest, Bitmap src) { 1138e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy final int[] xy = mTmpXY; 1139839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Bitmap mask = src.extractAlpha(mBlurPaint, xy); 1140e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mMaskCanvas.setBitmap(mask); 1141e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint); 11429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.drawColor(0, PorterDuff.Mode.CLEAR); 1143e80202d5c3c2a45cc34976e09958883e5366f1bbPatrick Dubroy dest.setMatrix(mIdentityMatrix); 1144839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint); 11459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mask.recycle(); 11469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 114732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 114844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen} 1149