StackView.java revision 5b53f9186e7812c93bc578d18e92cb123481fcbc
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 1944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport java.util.WeakHashMap; 2044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 2144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.animation.PropertyAnimator; 2244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.content.Context; 2332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohenimport android.graphics.Bitmap; 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; 3044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet; 3144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log; 3244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent; 3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker; 3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View; 3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration; 3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup; 3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView; 3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView 4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/** 4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe 4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children. 4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator { 4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final String TAG = "StackView"; 4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Default animation parameters 4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 505b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int DEFAULT_ANIMATION_DURATION = 400; 515b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int MINIMUM_ANIMATION_DURATION = 50; 5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These specify the different gesture states 5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 565b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_NONE = 0; 575b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_UP = 1; 585b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_DOWN = 2; 5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 6144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Specifies how far you need to swipe (up or down) before it 6244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * will be consider a completed gesture when you lift your finger 6344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 645b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SWIPE_THRESHOLD_RATIO = 0.35f; 655b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SLIDE_UP_RATIO = 0.7f; 6644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>(); 6844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final WeakHashMap<View, Integer> 6944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo = new WeakHashMap<View, Integer>(); 7044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 7144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 7244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Sentinel value for no current active pointer. 7344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Used by {@link #mActivePointerId}. 7444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 7544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private static final int INVALID_POINTER = -1; 7644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 7744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 7844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These variables are all related to the current state of touch interaction 7944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * with the stack 8044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialY; 8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialX; 8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mActivePointerId; 8444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mYVelocity = 0; 8544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeGestureType = GESTURE_NONE; 8644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mViewHeight; 8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeThreshold; 8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mTouchSlop; 8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mMaximumVelocity; 9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private VelocityTracker mVelocityTracker; 9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private ImageView mHighlight; 9332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private StackSlider mStackSlider; 9444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mFirstLayoutHappened = false; 9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // TODO: temp hack to get this thing started 9744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int mIndex = 5; 9844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context) { 10044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context); 10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context, AttributeSet attrs) { 10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context, attrs); 10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void initStackView() { 11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen configureViewAnimator(4, 2); 11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setStaticTransformationsEnabled(true); 11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 11344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mTouchSlop = configuration.getScaledTouchSlop();// + 5; 11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 11544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 11632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 11732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight = new ImageView(getContext()); 11832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.setLayoutParams(new LayoutParams(mHighlight)); 11932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight)); 12032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider = new StackSlider(); 12132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 12232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (!sPaintsInitialized) { 1235b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy initializePaints(); 12432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Animate the views between different relative indexes within the {@link AdapterViewAnimator} 12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void animateViewForTransition(int fromIndex, int toIndex, View view) { 13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (fromIndex == -1 && toIndex == 0) { 13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item in 13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (view.getAlpha() == 1) { 13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0); 13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, 13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view, "alpha", view.getAlpha(), 1.0f); 13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeIn.start(); 13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) { 14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item in 14144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setVisibility(VISIBLE); 14232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1455b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy int largestDuration = Math.round( 14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen (lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION); 14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = largestDuration; 14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mYVelocity != 0) { 14944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity); 15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 15144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 15244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.min(duration, largestDuration); 15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); 15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 15532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator slideInY = new PropertyAnimator(duration, mStackSlider, 15632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 0); 15732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideInY.start(); 15832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator slideInX = new PropertyAnimator(duration, mStackSlider, 15932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 16032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideInX.start(); 16144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 16244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) { 16344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item out 16444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 16544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1665b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy int largestDuration = Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION); 16744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = largestDuration; 16844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mYVelocity != 0) { 16944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity); 17044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 17144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 17244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.min(duration, largestDuration); 17344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); 17444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 17532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator slideOutY = new PropertyAnimator(duration, mStackSlider, 17632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 1); 17732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideOutY.start(); 17832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator slideOutX = new PropertyAnimator(duration, mStackSlider, 17932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 18032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideOutX.start(); 18144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 18244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) { 18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Make sure this view that is "waiting in the wings" is invisible 18444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0.0f); 18544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (toIndex == -1) { 18644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item out 18744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, 18844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view, "alpha", view.getAlpha(), 0); 18944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeOut.start(); 19044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 19144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 19244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Apply any necessary tranforms for the child that is being added. 19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void applyTransformForChildAtIndex(View child, int relativeIndex) { 19744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mRotations.containsKey(child)) { 1985b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy float rotation = (float) (Math.random()*26 - 13); 19944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mRotations.put(child, rotation); 20044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 20144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 20244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Child has been removed 20344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (relativeIndex == -1) { 20444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mRotations.containsKey(child)) { 20544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mRotations.remove(child); 20644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 20744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mChildrenToApplyTransformsTo.containsKey(child)) { 20844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo.remove(child); 20944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if this view is already in the layout, we need to 21344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // wait until layout has finished in order to set the 21444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // pivot point of the rotation (requiring getMeasuredWidth/Height()) 21544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo.put(child, relativeIndex); 21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 21744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 21844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 21944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 22044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super.onLayout(changed, left, top, right, bottom); 22144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 22244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mChildrenToApplyTransformsTo.isEmpty()) { 22344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (View child: mChildrenToApplyTransformsTo.keySet()) { 22444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mRotations.containsKey(child)) { 22544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen child.setPivotX(child.getMeasuredWidth()/2); 22644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen child.setPivotY(child.getMeasuredHeight()/2); 22744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen child.setRotation(mRotations.get(child)); 22844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 22944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 23044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo.clear(); 23144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 23244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 23344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mFirstLayoutHappened) { 2345b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight()); 2355b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight); 23644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 23744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // TODO: Right now this walks all the way up the view hierarchy and disables 23844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ClipChildren and ClipToPadding. We're probably going to want to reset 23944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // these flags as well. 24044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setClipChildren(false); 24144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen ViewGroup view = this; 24244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen while (view.getParent() != null && view.getParent() instanceof ViewGroup) { 24344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view = (ViewGroup) view.getParent(); 24444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setClipChildren(false); 24544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setClipToPadding(false); 24644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 24744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mFirstLayoutHappened = true; 24844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 24944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 25044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 25144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 25244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onInterceptTouchEvent(MotionEvent ev) { 25344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 25444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch(action & MotionEvent.ACTION_MASK) { 25544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 25644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_DOWN: { 25744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mActivePointerId == INVALID_POINTER) { 25844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX = ev.getX(); 25944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY = ev.getY(); 26044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(0); 26144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 26244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 26344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 26444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 26544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 26644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 26744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 26844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 27044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 27144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 27344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 27432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 27544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 27644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 27744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 27844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 27944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 28044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: 28244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 28344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 28444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 28544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 28844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return mSwipeGestureType != GESTURE_NONE; 28944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 29044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 29132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private void beginGestureIfNeeded(float deltaY) { 29232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 29332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 29432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen cancelLongPress(); 29532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen requestDisallowInterceptTouchEvent(true); 29632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 29732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen int activeIndex = mSwipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1 29832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen : mNumActiveViews - 2; 29932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 30032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen View v = getViewAtRelativeIndex(activeIndex); 30132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (v != null) { 30232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.setImageBitmap(createOutline(v)); 30332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 30432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen v.bringToFront(); 30532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setView(v); 30632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) 30732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen v.setVisibility(VISIBLE); 30832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 30932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 31032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 31132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 31244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 31344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onTouchEvent(MotionEvent ev) { 31444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 31544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 31644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 31744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 31844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 31944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 32044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 32144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 32244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 32332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float newX = ev.getX(pointerIndex); 32444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 32532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float deltaX = newX - mInitialX; 32644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker == null) { 32744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = VelocityTracker.obtain(); 32844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 32944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.addMovement(ev); 33044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 33144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch (action & MotionEvent.ACTION_MASK) { 33244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 33332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 33432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 33532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float rx = 0.3f*deltaX/(mViewHeight*1.0f); 33632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 33732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f; 33832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(1 - r); 33932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 34032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 34132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 34232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float r = -(deltaY + mTouchSlop*1.0f)/mViewHeight*1.0f; 34332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(r); 34432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 34532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 34644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 34844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 34944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 35044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: { 35144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 35244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 35344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 35444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 35544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 35644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 35744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 35844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 35944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 36044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 36144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 36244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 36544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 36744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final Rect touchRect = new Rect(); 36844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void onSecondaryPointerUp(MotionEvent ev) { 36944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int activePointerIndex = ev.getActionIndex(); 37044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int pointerId = ev.getPointerId(activePointerIndex); 37144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerId == mActivePointerId) { 37244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 37344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1 37444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen : mNumActiveViews - 2; 37544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 37644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(activeViewIndex); 37744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v == null) return; 37844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 37944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Our primary pointer has gone up -- let's see if we can find 38044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // another pointer on the view. If so, then we should replace 38144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // our primary pointer with this new pointer and adjust things 38244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so that the view doesn't jump 38344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (int index = 0; index < ev.getPointerCount(); index++) { 38444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (index != activePointerIndex) { 38544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 38644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float x = ev.getX(index); 38744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float y = ev.getY(index); 38844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 38944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 3905b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy if (touchRect.contains(Math.round(x), Math.round(y))) { 39144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldX = ev.getX(activePointerIndex); 39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldY = ev.getY(activePointerIndex); 39344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // adjust our frame of reference to avoid a jump 39544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY += (y - oldY); 39644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX += (x - oldX); 39744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(index); 39944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 40044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.clear(); 40144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ok, we're good, we found a new pointer which is touching the active view 40344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return; 40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if we made it this far, it means we didn't find a satisfactory new pointer :(, 40844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so end the 40944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 41344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void handlePointerUp(MotionEvent ev) { 41444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 41544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 41644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int deltaY = (int) (newY - mInitialY); 41744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 41844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 41944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); 42044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 42144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 42244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.recycle(); 42344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = null; 42444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 42632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN) { 42744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe down 42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen showNext(); 42932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 43032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP) { 43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe up 43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen showPrevious(); 43332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 43432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe up far enough, snap back down 4365b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy int duration = Math.round(mStackSlider.getYProgress()*DEFAULT_ANIMATION_DURATION); 43732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 43832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider, 43932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 0); 44032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackY.start(); 44132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator snapBackX = new PropertyAnimator(duration, mStackSlider, 44232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 44332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackX.start(); 44432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe down far enough, snap back up 4465b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy int duration = Math.round((1 - 44732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.getYProgress())*DEFAULT_ANIMATION_DURATION); 44832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator snapBackY = new PropertyAnimator(duration, mStackSlider, 44932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 1); 45032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackY.start(); 45132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen PropertyAnimator snapBackX = new PropertyAnimator(duration, mStackSlider, 45232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 45332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackX.start(); 45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 45544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 45644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 45744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 45832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 45932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 46032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private class StackSlider { 46132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen View mView; 46232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mYProgress; 46332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mXProgress; 46432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 46532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float cubic(float r) { 46632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return (float) (Math.pow(2*r-1, 3) + 1)/2.0f; 46732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 46832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 46932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float highlightAlphaInterpolator(float r) { 47032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.4f; 47132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r < pivot) { 47232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0.85f*cubic(r/pivot); 47332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 47432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0.85f*cubic(1 - (r-pivot)/(1-pivot)); 47532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 47632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 47732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 47832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float viewAlphaInterpolator(float r) { 47932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.3f; 48032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r > pivot) { 48132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return (r - pivot)/(1 - pivot); 48232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 48332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0; 48432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 48532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 48632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 48732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen void setView(View v) { 48832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mView = v; 48932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 49032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 49132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setYProgress(float r) { 49232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 49332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.min(1.0f, r); 49432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.max(0, r); 49532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 49632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mYProgress = r; 49732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 49832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 49932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 50032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 5015b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy viewLp.setVerticalOffset(Math.round(-r*mViewHeight)); 5025b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy highlightLp.setVerticalOffset(Math.round(-r*mViewHeight)); 50332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 50432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mView.setAlpha(viewAlphaInterpolator(1-r)); 50532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 50632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 50732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setXProgress(float r) { 50832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 50932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.min(1.0f, r); 51032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.max(-1.0f, r); 51132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 51232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mXProgress = r; 51332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 51432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 51532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 51632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 5175b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy viewLp.setHorizontalOffset(Math.round(r*mViewHeight)); 5185b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy highlightLp.setHorizontalOffset(Math.round(r*mViewHeight)); 51932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 52032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 52132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float getYProgress() { 52232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mYProgress; 52332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 52432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 52532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float getXProgress() { 52632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mXProgress; 52732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 52844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 52944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 53044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 53144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public void onRemoteAdapterConnected() { 53244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super.onRemoteAdapterConnected(); 53344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setDisplayedChild(mIndex); 53444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 53532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 53632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private static final Paint sHolographicPaint = new Paint(); 53732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private static final Paint sErasePaint = new Paint(); 53832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private static boolean sPaintsInitialized = false; 53932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private static final float STROKE_WIDTH = 3.0f; 54032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 5415b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy static void initializePaints() { 54232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen sHolographicPaint.setColor(0xff6699ff); 54332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen sHolographicPaint.setFilterBitmap(true); 54432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen sErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 54532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen sErasePaint.setFilterBitmap(true); 54632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen sPaintsInitialized = true; 54732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 54832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 54932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen static Bitmap createOutline(View v) { 55032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), 55132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Bitmap.Config.ARGB_8888); 55232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Canvas canvas = new Canvas(bitmap); 55332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 55432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen canvas.concat(v.getMatrix()); 55532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen v.draw(canvas); 55632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 55732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Bitmap outlineBitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), 55832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Bitmap.Config.ARGB_8888); 55932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Canvas outlineCanvas = new Canvas(outlineBitmap); 5605b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy drawOutline(outlineCanvas, bitmap); 56132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen bitmap.recycle(); 56232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return outlineBitmap; 56332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 56432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 5655b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy static void drawOutline(Canvas dest, Bitmap src) { 56632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen dest.drawColor(0, PorterDuff.Mode.CLEAR); 56732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 56832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Bitmap mask = src.extractAlpha(); 56932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Matrix id = new Matrix(); 57032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 57132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen Matrix m = new Matrix(); 57232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float xScale = STROKE_WIDTH*2/(src.getWidth()); 57332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float yScale = STROKE_WIDTH*2/(src.getHeight()); 57432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen m.preScale(1+xScale, 1+yScale, src.getWidth()/2, src.getHeight()/2); 57532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen dest.drawBitmap(mask, m, sHolographicPaint); 57632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 57732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen dest.drawBitmap(src, id, sErasePaint); 57832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mask.recycle(); 57932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 58044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen} 581