StackView.java revision 2794eb3b02e2404d453d3ad22a8a85a138130a07
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 19839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.animation.PropertyValuesHolder; 20a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haaseimport android.animation.ObjectAnimator; 2144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context; 22dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohenimport android.content.res.Resources; 2332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap; 24839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.BlurMaskFilter; 2532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Canvas; 2632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Matrix; 2732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Paint; 2832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuff; 2932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.PorterDuffXfermode; 3044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect; 319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF; 32839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.graphics.TableMaskFilter; 3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet; 34dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohenimport android.util.DisplayMetrics; 3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log; 3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent; 3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker; 3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View; 3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration; 4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup; 41839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.view.View.MeasureSpec; 42839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohenimport android.view.ViewGroup.LayoutParams; 43b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator; 4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView; 4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView 4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/** 4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe 4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children. 5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator { 5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final String TAG = "StackView"; 5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Default animation parameters 5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen private final int DEFAULT_ANIMATION_DURATION = 400; 58b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen private final int MINIMUM_ANIMATION_DURATION = 50; 5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 61839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Parameters effecting the perspective visuals 62839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 63839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f; 64839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static float PERSPECTIVE_SCALE_FACTOR = 0.35f; 65839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 66839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 67839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Represent the two possible stack modes, one where items slide up, and the other 68839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * where items slide down. The perspective is also inverted between these two modes. 69839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 70839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_UP = 0; 71839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int ITEMS_SLIDE_DOWN = 1; 72839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 73839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 7444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These specify the different gesture states 7544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 765b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_NONE = 0; 775b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_UP = 1; 785b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_DOWN = 2; 7944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 8044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Specifies how far you need to swipe (up or down) before it 8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * will be consider a completed gesture when you lift your finger 8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 845b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SWIPE_THRESHOLD_RATIO = 0.35f; 855b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SLIDE_UP_RATIO = 0.7f; 8644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Sentinel value for no current active pointer. 8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Used by {@link #mActivePointerId}. 9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private static final int INVALID_POINTER = -1; 9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 94839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen * Number of active views in the stack. One fewer view is actually visible, as one is hidden. 95839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen */ 96839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private static final int NUM_ACTIVE_VIEWS = 5; 97839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 98dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private static final int FRAME_PADDING = 4; 99839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 100839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen /** 10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These variables are all related to the current state of touch interaction 10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * with the stack 10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialY; 10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialX; 10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mActivePointerId; 10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mYVelocity = 0; 10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeGestureType = GESTURE_NONE; 109dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mSlideAmount; 11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeThreshold; 11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mTouchSlop; 11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mMaximumVelocity; 11344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private VelocityTracker mVelocityTracker; 11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static HolographicHelper sHolographicHelper; 11632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private ImageView mHighlight; 11732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private StackSlider mStackSlider; 11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mFirstLayoutHappened = false; 1199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private ViewGroup mAncestorContainingAllChildren = null; 1209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private int mAncestorHeight = 0; 121839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private int mStackMode; 122dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen private int mFramePadding; 12344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context) { 12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context); 12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context, AttributeSet attrs) { 13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context, attrs); 13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void initStackView() { 1351b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2); 13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setStaticTransformationsEnabled(true); 13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 138b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mTouchSlop = configuration.getScaledTouchSlop(); 13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 14132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 14232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight = new ImageView(getContext()); 14332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.setLayoutParams(new LayoutParams(mHighlight)); 14432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight)); 14532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider = new StackSlider(); 14632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 1479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (sHolographicHelper == null) { 148dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen sHolographicHelper = new HolographicHelper(mContext); 14932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 1509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipChildren(false); 1519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipToPadding(false); 1521480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen 153839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // This sets the form of the StackView, which is currently to have the perspective-shifted 154839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // views above the active view, and have items slide down when sliding out. The opposite is 155839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // available by using ITEMS_SLIDE_UP. 156839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mStackMode = ITEMS_SLIDE_DOWN; 157839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 1581480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // This is a flag to indicate the the stack is loading for the first time 1591480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen mWhichChild = -1; 160dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 161dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // Adjust the frame padding based on the density, since the highlight changes based 162dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen // on the density 163dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen final float density = mContext.getResources().getDisplayMetrics().density; 164dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mFramePadding = (int) Math.ceil(density * FRAME_PADDING); 16544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 16644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 16744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 16844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Animate the views between different relative indexes within the {@link AdapterViewAnimator} 16944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 17044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void animateViewForTransition(int fromIndex, int toIndex, View view) { 17144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (fromIndex == -1 && toIndex == 0) { 17244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item in 17344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (view.getAlpha() == 1) { 17444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0); 17544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 176b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(VISIBLE); 177b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 1782794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f); 1792794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase fadeIn.setDuration(DEFAULT_ANIMATION_DURATION); 18044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeIn.start(); 18144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) { 18244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item in 18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setVisibility(VISIBLE); 18432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 18544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 186839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity)); 18744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 188b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 1892794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f); 1902794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 1912794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 192839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen slideInX, slideInY); 1932794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 194839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 195839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) { 19744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item out 19844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 19944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 200839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity)); 20144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 202b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 2032794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f); 2042794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 2052794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 2062794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase slideOutX, slideOutY); 2072794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 208839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 209839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) { 21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Make sure this view that is "waiting in the wings" is invisible 21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0.0f); 213b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(INVISIBLE); 214b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 215dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen lp.setVerticalOffset(-mSlideAmount); 21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (toIndex == -1) { 21744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item out 2182794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f); 2192794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase fadeOut.setDuration(DEFAULT_ANIMATION_DURATION); 22044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeOut.start(); 22144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 222839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 223839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // Implement the faked perspective 224839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (toIndex != -1) { 225f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen transformViewAtIndex(toIndex, view); 226f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 227f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 228839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 229f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen private void transformViewAtIndex(int index, View view) { 230dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float maxPerpectiveShift = mMeasuredHeight * PERSPECTIVE_SHIFT_FACTOR; 231839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 232f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen if (index == mNumActiveViews -1) index--; 233839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 234f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen float r = (index * 1.0f) / (mNumActiveViews - 2); 235839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 236f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r); 2372794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", scale); 2382794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", scale); 239839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 240f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen r = (float) Math.pow(r, 2); 241839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 242f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; 243dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float perspectiveTranslation = -stackDirection * r * maxPerpectiveShift; 244dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float scaleShiftCorrection = stackDirection * (1 - scale) * 245dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen (mMeasuredHeight * (1 - PERSPECTIVE_SHIFT_FACTOR) / 2.0f); 246dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float transY = perspectiveTranslation + scaleShiftCorrection; 247f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen 2482794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY); 2492794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY, translationY); 2502794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(100); 251f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen pa.start(); 252f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 253f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen 254f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen private void updateChildTransforms() { 255f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen for (int i = 0; i < mNumActiveViews - 1; i++) { 256f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen View v = getViewAtRelativeIndex(i); 257f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen if (v != null) { 258f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen transformViewAtIndex(i, v); 259f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen } 260839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 26144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 26244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 263dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen @Override 264dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen FrameLayout getFrameForChild() { 265dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen FrameLayout fl = new FrameLayout(mContext); 266dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding); 267dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen return fl; 268dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen } 269dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 27044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 27144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Apply any necessary tranforms for the child that is being added. 27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 27344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void applyTransformForChildAtIndex(View child, int relativeIndex) { 27444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 27544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 27644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 2779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void dispatchDraw(Canvas canvas) { 2789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super.dispatchDraw(canvas); 2799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 2809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 2819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // TODO: right now, this code walks up the hierarchy as far as needed and disables clipping 2829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // so that the stack's children can draw outside of the stack's bounds. This is fine within 2839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // the context of widgets in the launcher, but is destructive in general, as the clipping 2849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // values are not being reset. For this to be a full framework level widget, we will need 2859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // framework level support for drawing outside of a parent's bounds. 2869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private void disableParentalClipping() { 2879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mAncestorContainingAllChildren != null) { 2889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen ViewGroup vg = this; 2899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) { 2909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (vg == mAncestorContainingAllChildren) break; 2919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen vg = (ViewGroup) vg.getParent(); 2929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen vg.setClipChildren(false); 2939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen vg.setClipToPadding(false); 29444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 29544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 2969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 29744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 2989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private void onLayout() { 29944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mFirstLayoutHappened) { 300dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight()); 301f04e22571f17bf850ef89adddf2b12171e536619Adam Cohen updateChildTransforms(); 302dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount); 30344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mFirstLayoutHappened = true; 30444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 30544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 30644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 30744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 30844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onInterceptTouchEvent(MotionEvent ev) { 30944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 31044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch(action & MotionEvent.ACTION_MASK) { 31144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_DOWN: { 31244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mActivePointerId == INVALID_POINTER) { 31344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX = ev.getX(); 31444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY = ev.getY(); 31544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(0); 31644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 31744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 31844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 31944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 32044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 32144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 32244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 32344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 32444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 32544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 32644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 32744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 32844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 32932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 33044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 33144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 33244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 33344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 33444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 33544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 33644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: 33744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 33844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 33944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 34044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 34344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return mSwipeGestureType != GESTURE_NONE; 34444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 34632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private void beginGestureIfNeeded(float deltaY) { 34732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 3483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 34932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen cancelLongPress(); 35032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen requestDisallowInterceptTouchEvent(true); 35132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 352839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int activeIndex; 353839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 354839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 355839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mNumActiveViews - 1 : mNumActiveViews - 2; 356839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 357839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 358839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mNumActiveViews - 2 : mNumActiveViews - 1; 359839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 36032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 3613d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mAdapter == null) return; 3623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3631b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen if (mLoopViews) { 3641b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen mStackSlider.setMode(StackSlider.NORMAL_MODE); 3651b065cd1401253f999caa5d0ac12909407cef00eAdam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == 0) { 3663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE); 3673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) { 3683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen activeIndex--; 3693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setMode(StackSlider.END_OF_STACK_MODE); 3703d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else { 3713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setMode(StackSlider.NORMAL_MODE); 37232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 3733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen View v = getViewAtRelativeIndex(activeIndex); 3753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (v == null) return; 3763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHighlight.setImageBitmap(sHolographicHelper.createOutline(v)); 378839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHighlight.setRotation(v.getRotation()); 379839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHighlight.setTranslationY(v.getTranslationY()); 3803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.bringToFront(); 3813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen v.bringToFront(); 3823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setView(v); 3833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (swipeGestureType == GESTURE_SLIDE_DOWN) 3853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen v.setVisibility(VISIBLE); 3863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We only register this gesture if we've made it this far without a problem 3883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mSwipeGestureType = swipeGestureType; 38932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 39032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 39132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 39344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onTouchEvent(MotionEvent ev) { 39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 39544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 39644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 39744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 39944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 40044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 40244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 40332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float newX = ev.getX(pointerIndex); 40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 40532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float deltaX = newX - mInitialX; 40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker == null) { 40744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = VelocityTracker.obtain(); 40844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.addMovement(ev); 41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch (action & MotionEvent.ACTION_MASK) { 41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 41332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 41432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 415dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float rx = deltaX / (mSlideAmount * 1.0f); 41632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 417dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 418839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 41932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(1 - r); 42032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 42132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 42232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 423dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f; 424839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; 42532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(r); 42632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 42732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 43044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: { 43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 43344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 43444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 43644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 43744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 43844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 44044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 44744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 44844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final Rect touchRect = new Rect(); 44944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void onSecondaryPointerUp(MotionEvent ev) { 45044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int activePointerIndex = ev.getActionIndex(); 45144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int pointerId = ev.getPointerId(activePointerIndex); 45244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerId == mActivePointerId) { 45344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1 45544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen : mNumActiveViews - 2; 45644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 45744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(activeViewIndex); 45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v == null) return; 45944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 46044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Our primary pointer has gone up -- let's see if we can find 46144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // another pointer on the view. If so, then we should replace 46244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // our primary pointer with this new pointer and adjust things 46344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so that the view doesn't jump 46444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (int index = 0; index < ev.getPointerCount(); index++) { 46544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (index != activePointerIndex) { 46644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 46744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float x = ev.getX(index); 46844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float y = ev.getY(index); 46944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 47044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 4715b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy if (touchRect.contains(Math.round(x), Math.round(y))) { 47244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldX = ev.getX(activePointerIndex); 47344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldY = ev.getY(activePointerIndex); 47444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 47544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // adjust our frame of reference to avoid a jump 47644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY += (y - oldY); 47744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX += (x - oldX); 47844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 47944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(index); 48044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 48144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.clear(); 48244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 48344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ok, we're good, we found a new pointer which is touching the active view 48444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return; 48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 48844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if we made it this far, it means we didn't find a satisfactory new pointer :(, 4893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so end the gesture 49044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 49144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 49244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 49344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 49444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void handlePointerUp(MotionEvent ev) { 49544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 49644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 49744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int deltaY = (int) (newY - mInitialY); 49844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mVelocityTracker != null) { 5003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 5013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); 5023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 50344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 50444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 50544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.recycle(); 50644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = null; 50744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 50844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN 5103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 51144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe down 512839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 513839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showNext(); 514839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 515839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showPrevious(); 516839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 51732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 5183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP 5193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 52044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe up 521839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP) { 522839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showPrevious(); 523839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 524839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen showNext(); 525839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 526839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 52732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 528839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) { 52944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe up far enough, snap back down 530839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 531839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0; 532839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 533839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 534839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 535839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 536839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 53732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 538b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 5392794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress); 5402794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 5412794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 542839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 5432794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 544839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.setInterpolator(new LinearInterpolator()); 545839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 54632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 54744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe down far enough, snap back up 548839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1; 549839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int duration; 550839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) { 551839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForNeutralPosition()); 552839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 553839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); 554839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 5553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 556b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 5572794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackY = 5582794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder.ofFloat("YProgress",finalYProgress); 5592794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f); 5602794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider, 561839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen snapBackX, snapBackY); 5622794eb3b02e2404d453d3ad22a8a85a138130a07Chet Haase pa.setDuration(duration); 563839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen pa.start(); 56444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 56544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 56644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 56744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 56832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 56932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 57032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private class StackSlider { 57132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen View mView; 57232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mYProgress; 57332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mXProgress; 57432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 5753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int NORMAL_MODE = 0; 5763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int BEGINNING_OF_STACK_MODE = 1; 5773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int END_OF_STACK_MODE = 2; 5783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 5793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int mMode = NORMAL_MODE; 5803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 581b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider() { 582b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 583b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 584b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider(StackSlider copy) { 585b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mView = copy.mView; 586b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mYProgress = copy.mYProgress; 587b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mXProgress = copy.mXProgress; 5883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = copy.mMode; 589b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 590b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 59132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float cubic(float r) { 592839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f; 59332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 59432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 59532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float highlightAlphaInterpolator(float r) { 59632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.4f; 59732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r < pivot) { 598839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(r / pivot); 59932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 600839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return 0.85f * cubic(1 - (r - pivot) / (1 - pivot)); 60132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 60232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 60332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 60432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float viewAlphaInterpolator(float r) { 60532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.3f; 60632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r > pivot) { 607839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 60832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 60932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0; 61032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 61132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 61232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 613b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen private float rotationInterpolator(float r) { 614b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen float pivot = 0.2f; 615b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen if (r < pivot) { 616b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen return 0; 617b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } else { 618839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (r - pivot) / (1 - pivot); 619b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 620b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 621b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 62232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen void setView(View v) { 62332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mView = v; 62432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 62532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 62632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setYProgress(float r) { 62732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 62832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.min(1.0f, r); 62932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.max(0, r); 63032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 63132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mYProgress = r; 63232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 63332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 63432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 635839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; 636839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 6373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen switch (mMode) { 6383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case NORMAL_MODE: 639dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 640dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount)); 6413d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 6423d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 643839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float alpha = viewAlphaInterpolator(1 - r); 6443d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6453d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We make sure that views which can't be seen (have 0 alpha) are also invisible 6463d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so that they don't interfere with click events. 6473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) { 6483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(VISIBLE); 6493d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (alpha == 0 && mView.getAlpha() != 0 6503d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mView.getVisibility() == VISIBLE) { 6513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(INVISIBLE); 6523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 653b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 6543d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setAlpha(alpha); 655839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 656839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); 6573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 6583d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case BEGINNING_OF_STACK_MODE: 659839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = r * 0.2f; 660dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 661dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount)); 6623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 6633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 6643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case END_OF_STACK_MODE: 665839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen r = (1-r) * 0.2f; 666dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 667dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount)); 6683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 6693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 670b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 67132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 67232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 67332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setXProgress(float r) { 67432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 6753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.min(2.0f, r); 6763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.max(-2.0f, r); 67732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 67832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mXProgress = r; 67932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 68032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 68132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 68232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r *= 0.2f; 684dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen viewLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 685dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount)); 68632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 68732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen void setMode(int mode) { 6893d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = mode; 6903d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 6913d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6923d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForNeutralPosition() { 693839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, 0); 6943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 6953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForOffscreenPosition() { 697839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, 0); 698839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 699839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 700839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForNeutralPosition(float velocity) { 701839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(false, velocity); 702839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 703839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 704839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float getDurationForOffscreenPosition(float velocity) { 705839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(true, velocity); 7063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 7073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 708839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private float getDuration(boolean invert, float velocity) { 7093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView != null) { 7103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 7113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 712839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) + 713839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Math.pow(viewLp.verticalOffset, 2)); 714dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) + 715dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen Math.pow(0.4f * mSlideAmount, 2)); 716839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 717839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (velocity == 0) { 718839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION; 719839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 720839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float duration = invert ? d / Math.abs(velocity) : 721839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen (maxd - d) / Math.abs(velocity); 722839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (duration < MINIMUM_ANIMATION_DURATION || 723839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen duration > DEFAULT_ANIMATION_DURATION) { 724839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return getDuration(invert, 0); 725839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else { 726839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen return duration; 727839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 728839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 7293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 7303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return 0; 7313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 7323d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 733839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getYProgress() { 73432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mYProgress; 73532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 73632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 737839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen public float getXProgress() { 73832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mXProgress; 73932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 74044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 74144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 74244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 74344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public void onRemoteAdapterConnected() { 74444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super.onRemoteAdapterConnected(); 7451480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // On first run, we want to set the stack to the end. 7461480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen if (mAdapter != null && mWhichChild == -1) { 7471480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen mWhichChild = mAdapter.getCount() - 1; 7481480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen } 7493042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen if (mWhichChild >= 0) { 7503042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen setDisplayedChild(mWhichChild); 7513042944c6ec68210ba1746540b53789e70d15ef4Adam Cohen } 75244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 75332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams createOrReuseLayoutParams(View v) { 7559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); 7569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (currentLp instanceof LayoutParams) { 7579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) currentLp; 7589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setHorizontalOffset(0); 7599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setVerticalOffset(0); 760839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 761839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen lp.width = 0; 7629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return lp; 7639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return new LayoutParams(v); 76532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 76632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen @Override 7689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 7699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean dataChanged = mDataChanged; 7709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (dataChanged) { 7719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen handleDataChanged(); 7729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // if the data changes, mWhichChild might be out of the bounds of the adapter 7749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // in this case, we reset mWhichChild to the beginning 7759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mWhichChild >= mAdapter.getCount()) 7769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mWhichChild = 0; 7779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen showOnly(mWhichChild, true, true); 7796364f2bbe5254b4274f3feffc48f4259eacc205eWinson Chung refreshChildren(); 7809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final int childCount = getChildCount(); 7839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen for (int i = 0; i < childCount; i++) { 7849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final View child = getChildAt(i); 7859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childRight = mPaddingLeft + child.getMeasuredWidth(); 7879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childBottom = mPaddingTop + child.getMeasuredHeight(); 7889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) child.getLayoutParams(); 7899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, 7919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); 7929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mDataChanged = false; 7969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen onLayout(); 7979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 799839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private void measureChildren() { 800839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int count = getChildCount(); 801839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight; 802839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int childHeight = Math.round(mMeasuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR)) 803839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen - mPaddingTop - mPaddingBottom; 804839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 805839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen for (int i = 0; i < count; i++) { 806839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final View child = getChildAt(i); 807839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), 808839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); 809839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 810839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 811839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 812839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen @Override 813839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 814839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); 815839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); 816839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); 817839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); 818839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 819839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); 820839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 821839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // We need to deal with the case where our parent hasn't told us how 822839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // big we should be. In this case we should 823839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR); 824839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (heightSpecMode == MeasureSpec.UNSPECIFIED) { 825839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen heightSpecSize = haveChildRefSize ? 826839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Math.round(mReferenceChildHeight * (1 + factor)) + 827839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingTop + mPaddingBottom : 0; 828839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 829839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen heightSpecSize = haveChildRefSize ? Math.min( 830839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Math.round(mReferenceChildHeight * (1 + factor)) + mPaddingTop + 831839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingBottom, heightSpecSize) : 0; 832839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 833839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 834839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen if (widthSpecMode == MeasureSpec.UNSPECIFIED) { 835839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + 836839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingRight : 0; 837839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } else if (heightSpecMode == MeasureSpec.AT_MOST) { 838839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft + 839839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mPaddingRight, widthSpecSize) : 0; 840839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 841839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 842839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen setMeasuredDimension(widthSpecSize, heightSpecSize); 843839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen measureChildren(); 844839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen } 845839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 8469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen class LayoutParams extends ViewGroup.LayoutParams { 8479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int horizontalOffset; 8489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int verticalOffset; 8499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View mView; 8509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(View view) { 8529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(0, 0); 853839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 854839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 8559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 8569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 8579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView = view; 8589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(Context c, AttributeSet attrs) { 8619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(c, attrs); 8629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 8639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 864839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen width = 0; 865839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen height = 0; 866b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 867b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 8689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private Rect parentRect = new Rect(); 8699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void invalidateGlobalRegion(View v, Rect r) { 8709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View p = v; 8719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!(v.getParent() != null && v.getParent() instanceof View)) return; 8729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean firstPass = true; 8749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen parentRect.set(0, 0, 0, 0); 8759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int depth = 0; 876b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen while (p.getParent() != null && p.getParent() instanceof View 8779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen && !parentRect.contains(r)) { 8789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!firstPass) { 879b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen r.offset(p.getLeft() - p.getScrollX(), p.getTop() - p.getScrollY()); 8809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen depth++; 8819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen firstPass = false; 8839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p = (View) p.getParent(); 884b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen parentRect.set(p.getScrollX(), p.getScrollY(), 885b7f4d030a2ed9301bf47c41fefc1b338f4347ffeAdam Cohen p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY()); 886839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen 887839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // TODO: we need to stop early here if we've hit the edge of the screen 888839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // so as to prevent us from walking too high in the hierarchy. A lot of this 889839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen // code might become a lot more straightforward. 8909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (depth > mAncestorHeight) { 8939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mAncestorContainingAllChildren = (ViewGroup) p; 8949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mAncestorHeight = depth; 8959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen disableParentalClipping(); 8969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p.invalidate(r.left, r.top, r.right, r.bottom); 8999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 90032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 9019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private Rect invalidateRect = new Rect(); 9029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private RectF invalidateRectf = new RectF(); 903a18a86b43e40e3c15dcca0ae0148d641be9b25feChet Haase // This is public so that ObjectAnimator can access it 9049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setVerticalOffset(int newVerticalOffset) { 9059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int offsetDelta = newVerticalOffset - verticalOffset; 9069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = newVerticalOffset; 90732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 9089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mView != null) { 9099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.requestLayout(); 9109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int top = Math.min(mView.getTop() + offsetDelta, mView.getTop()); 9119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom()); 9129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.set(mView.getLeft(), top, mView.getRight(), bottom); 9149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xoffset = -invalidateRectf.left; 9169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yoffset = -invalidateRectf.top; 9179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(xoffset, yoffset); 9189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.getMatrix().mapRect(invalidateRectf); 9199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(-xoffset, -yoffset); 9209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRect.set((int) Math.floor(invalidateRectf.left), 9219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.floor(invalidateRectf.top), 9229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.right), 9239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.bottom)); 9249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateGlobalRegion(mView, invalidateRect); 9269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setHorizontalOffset(int newHorizontalOffset) { 9309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int offsetDelta = newHorizontalOffset - horizontalOffset; 9319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = newHorizontalOffset; 9329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mView != null) { 9349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.requestLayout(); 9359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft()); 9369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int right = Math.max(mView.getRight() + offsetDelta, mView.getRight()); 9379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.set(left, mView.getTop(), right, mView.getBottom()); 9389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xoffset = -invalidateRectf.left; 9409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yoffset = -invalidateRectf.top; 9419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(xoffset, yoffset); 9429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.getMatrix().mapRect(invalidateRectf); 9439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(-xoffset, -yoffset); 9449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRect.set((int) Math.floor(invalidateRectf.left), 9469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.floor(invalidateRectf.top), 9479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.right), 9489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.bottom)); 9499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateGlobalRegion(mView, invalidateRect); 9519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 95332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 95432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 9559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static class HolographicHelper { 9569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mHolographicPaint = new Paint(); 9579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mErasePaint = new Paint(); 958839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen private final Paint mBlurPaint = new Paint(); 95932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 960dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen HolographicHelper(Context context) { 961dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen initializePaints(context); 9629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 964dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen void initializePaints(Context context) { 965dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen final float density = context.getResources().getDisplayMetrics().density; 966dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen 9679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHolographicPaint.setColor(0xff6699ff); 9689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHolographicPaint.setFilterBitmap(true); 969839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); 9709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 9719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setFilterBitmap(true); 972dfcdddd7c408dddb22fb0867e4799d4c29d2f55fAdam Cohen mBlurPaint.setMaskFilter(new BlurMaskFilter(2*density, BlurMaskFilter.Blur.NORMAL)); 9739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap createOutline(View v) { 9769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) { 9779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return null; 9789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), 9819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap.Config.ARGB_8888); 9829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Canvas canvas = new Canvas(bitmap); 98332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 9849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float rotationX = v.getRotationX(); 985839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float rotation = v.getRotation(); 986839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen float translationY = v.getTranslationY(); 9879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(0); 988839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(0); 989839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(0); 9909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.draw(canvas); 9919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(rotationX); 992839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setRotation(rotation); 993839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen v.setTranslationY(translationY); 9949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen drawOutline(canvas, bitmap); 9969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return bitmap; 9979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 9989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 9999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final Matrix id = new Matrix(); 10009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void drawOutline(Canvas dest, Bitmap src) { 1001839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen int[] xy = new int[2]; 1002839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Bitmap mask = src.extractAlpha(mBlurPaint, xy); 1003839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen Canvas maskCanvas = new Canvas(mask); 1004839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen maskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint); 10059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.drawColor(0, PorterDuff.Mode.CLEAR); 10069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.setMatrix(id); 1007839f4a54e5a6fe602dbc5998b01412d809eba722Adam Cohen dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint); 10089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mask.recycle(); 10099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 101032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 101144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen} 1012