StackView.java revision 44729e3d1c01265858eec566c7b7c676c46a7916
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; 2344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.graphics.Rect; 2444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet; 2544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log; 2644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent; 2744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker; 2844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View; 2944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration; 3044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup; 3144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView; 3244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView 3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/** 3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe 3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children. 3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 3844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator { 3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final String TAG = "StackView"; 4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Default animation parameters 4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final int DEFAULT_ANIMATION_DURATION = 400; 4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final int MINIMUM_ANIMATION_DURATION = 50; 4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These specify the different gesture states 4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final int GESTURE_NONE = 0; 5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final int GESTURE_SLIDE_UP = 1; 5244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final int GESTURE_SLIDE_DOWN = 2; 5344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Specifies how far you need to swipe (up or down) before it 5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * will be consider a completed gesture when you lift your finger 5744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 5844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final float SWIPE_THRESHOLD_RATIO = 0.35f; 5944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final float SLIDE_UP_RATIO = 0.7f; 6044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>(); 6244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final WeakHashMap<View, Integer> 6344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo = new WeakHashMap<View, Integer>(); 6444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 6644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Sentinel value for no current active pointer. 6744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Used by {@link #mActivePointerId}. 6844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 6944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private static final int INVALID_POINTER = -1; 7044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 7144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 7244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These variables are all related to the current state of touch interaction 7344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * with the stack 7444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 7544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mGestureComplete = false; 7644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialY; 7744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private float mInitialX; 7844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mActivePointerId; 7944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mYOffset = 0; 8044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mYVelocity = 0; 8144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeGestureType = GESTURE_NONE; 8244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mViewHeight; 8344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mSwipeThreshold; 8444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mTouchSlop; 8544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private int mMaximumVelocity; 8644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private VelocityTracker mVelocityTracker; 8744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 8844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mFirstLayoutHappened = false; 8944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // TODO: temp hack to get this thing started 9144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int mIndex = 5; 9244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context) { 9444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context); 9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 9644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 9744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 9844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public StackView(Context context, AttributeSet attrs) { 9944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super(context, attrs); 10044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen initStackView(); 10144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 10244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 10344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void initStackView() { 10444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen configureViewAnimator(4, 2); 10544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setStaticTransformationsEnabled(true); 10644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 10744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mTouchSlop = configuration.getScaledTouchSlop();// + 5; 10844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 10944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 11044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 11344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Animate the views between different relative indexes within the {@link AdapterViewAnimator} 11444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 11544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void animateViewForTransition(int fromIndex, int toIndex, View view) { 11644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (fromIndex == -1 && toIndex == 0) { 11744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item in 11844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (view.getAlpha() == 1) { 11944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0); 12044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 12144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, 12244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view, "alpha", view.getAlpha(), 1.0f); 12344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeIn.start(); 12444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) { 12544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item in 12644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setVisibility(VISIBLE); 12744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 12844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 12944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int largestDuration = (int) Math.round( 13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen (lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION); 13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = largestDuration; 13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mYVelocity != 0) { 13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity); 13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.min(duration, largestDuration); 13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); 13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator slideDown = new PropertyAnimator(duration, lp, 14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "verticalOffset", lp.verticalOffset, 0); 14144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen slideDown.start(); 14244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeIn = new PropertyAnimator(duration, view, 14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "alpha", view.getAlpha(), 1.0f); 14544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeIn.start(); 14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) { 14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item out 14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 14944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int largestDuration = (int) Math.round( 15144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen (1 - (lp.verticalOffset*1.0f/-mViewHeight))*DEFAULT_ANIMATION_DURATION); 15244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = largestDuration; 15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mYVelocity != 0) { 15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity); 15544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 15644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 15744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.min(duration, largestDuration); 15844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); 15944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 16044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator slideUp = new PropertyAnimator(duration, lp, 16144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "verticalOffset", lp.verticalOffset, -mViewHeight); 16244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen slideUp.start(); 16344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 16444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeOut = new PropertyAnimator(duration, view, 16544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "alpha", view.getAlpha(), 0.0f); 16644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeOut.start(); 16744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) { 16844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Make sure this view that is "waiting in the wings" is invisible 16944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0.0f); 17044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (toIndex == -1) { 17144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item out 17244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, 17344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view, "alpha", view.getAlpha(), 0); 17444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeOut.start(); 17544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 17644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 17744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 17844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 17944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Apply any necessary tranforms for the child that is being added. 18044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 18144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void applyTransformForChildAtIndex(View child, int relativeIndex) { 18244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float rotation; 18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 18444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mRotations.containsKey(child)) { 18544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen rotation = (float) (Math.random()*26 - 13); 18644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mRotations.put(child, rotation); 18744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else { 18844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen rotation = mRotations.get(child); 18944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 19044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 19144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Child has been removed 19244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (relativeIndex == -1) { 19344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mRotations.containsKey(child)) { 19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mRotations.remove(child); 19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mChildrenToApplyTransformsTo.containsKey(child)) { 19744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo.remove(child); 19844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 19944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 20044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 20144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if this view is already in the layout, we need to 20244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // wait until layout has finished in order to set the 20344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // pivot point of the rotation (requiring getMeasuredWidth/Height()) 20444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo.put(child, relativeIndex); 20544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 20644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 20744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 20844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 20944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super.onLayout(changed, left, top, right, bottom); 21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mChildrenToApplyTransformsTo.isEmpty()) { 21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (View child: mChildrenToApplyTransformsTo.keySet()) { 21344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mRotations.containsKey(child)) { 21444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen child.setPivotX(child.getMeasuredWidth()/2); 21544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen child.setPivotY(child.getMeasuredHeight()/2); 21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen child.setRotation(mRotations.get(child)); 21744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 21844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 21944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mChildrenToApplyTransformsTo.clear(); 22044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 22144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 22244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mFirstLayoutHappened) { 22344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mViewHeight = (int) Math.round(SLIDE_UP_RATIO*getMeasuredHeight()); 22444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeThreshold = (int) Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight); 22544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 22644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // TODO: Right now this walks all the way up the view hierarchy and disables 22744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ClipChildren and ClipToPadding. We're probably going to want to reset 22844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // these flags as well. 22944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setClipChildren(false); 23044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen ViewGroup view = this; 23144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen while (view.getParent() != null && view.getParent() instanceof ViewGroup) { 23244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view = (ViewGroup) view.getParent(); 23344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setClipChildren(false); 23444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setClipToPadding(false); 23544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 23644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 23744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mFirstLayoutHappened = true; 23844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 23944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 24044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 24144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 24244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onInterceptTouchEvent(MotionEvent ev) { 24344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 24444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch(action & MotionEvent.ACTION_MASK) { 24544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 24644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_DOWN: { 24744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mActivePointerId == INVALID_POINTER) { 24844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX = ev.getX(); 24944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY = ev.getY(); 25044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(0); 25144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 25244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 25344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 25444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 25544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 25644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 25744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 25844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 25944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 26044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 26144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 26244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 26344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 26444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 26544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 26644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 26744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mGestureComplete = false; 26844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen cancelLongPress(); 26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen requestDisallowInterceptTouchEvent(true); 27044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 27144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 27344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 27444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 27544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 27644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 27744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: 27844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 27944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 28044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 28144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mGestureComplete = true; 28244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 28544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return mSwipeGestureType != GESTURE_NONE; 28644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 28844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 28944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onTouchEvent(MotionEvent ev) { 29044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 29144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 29244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 29344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 29444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 29544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 29644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 29744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 29844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 29944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 30044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 30144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker == null) { 30244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = VelocityTracker.obtain(); 30344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 30444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.addMovement(ev); 30544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 30644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch (action & MotionEvent.ACTION_MASK) { 30744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 30844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 30944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 31044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mGestureComplete = false; 31144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen cancelLongPress(); 31244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen requestDisallowInterceptTouchEvent(true); 31344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 31444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 31544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mGestureComplete) { 31644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 31744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(mNumActiveViews - 1); 31844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v != null) { 31944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // This view is present but hidden, make sure it's visible 32044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if they pull down 32144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen v.setVisibility(VISIBLE); 32244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 32344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float r = (deltaY-mTouchSlop)*1.0f / (mSwipeThreshold); 32444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mYOffset = Math.min(-mViewHeight + (int) Math.round( 32544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen r*mSwipeThreshold) - mTouchSlop, 0); 32644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) v.getLayoutParams(); 32744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen lp.setVerticalOffset(mYOffset); 32844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 32944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight)); 33044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen alpha = Math.min(1.0f, alpha); 33144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen v.setAlpha(alpha); 33244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 33344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 33444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 33544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(mNumActiveViews - 2); 33644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 33744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v != null) { 33844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float r = -(deltaY*1.0f + mTouchSlop) / (mSwipeThreshold); 33944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mYOffset = Math.min((int) Math.round(r*-mSwipeThreshold), 0); 34044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) v.getLayoutParams(); 34144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen lp.setVerticalOffset(mYOffset); 34244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 34344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float alpha = Math.max(0.0f, 1.0f - (1.0f*mYOffset/-mViewHeight)); 34444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen alpha = Math.min(1.0f, alpha); 34544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen v.setAlpha(alpha); 34644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 34844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 35044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 35144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 35244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: { 35344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 35444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 35544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 35644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 35744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 35844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 35944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 36144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 36244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mGestureComplete = true; 36344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 36444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mYOffset = 0; 36544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 36644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 36944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 37044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 37144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final Rect touchRect = new Rect(); 37244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void onSecondaryPointerUp(MotionEvent ev) { 37344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int activePointerIndex = ev.getActionIndex(); 37444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int pointerId = ev.getPointerId(activePointerIndex); 37544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerId == mActivePointerId) { 37644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 37744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1 37844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen : mNumActiveViews - 2; 37944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 38044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(activeViewIndex); 38144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v == null) return; 38244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 38344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Our primary pointer has gone up -- let's see if we can find 38444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // another pointer on the view. If so, then we should replace 38544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // our primary pointer with this new pointer and adjust things 38644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so that the view doesn't jump 38744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (int index = 0; index < ev.getPointerCount(); index++) { 38844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (index != activePointerIndex) { 38944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float x = ev.getX(index); 39144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float y = ev.getY(index); 39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (touchRect.contains((int) Math.round(x), (int) Math.round(y))) { 39544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldX = ev.getX(activePointerIndex); 39644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldY = ev.getY(activePointerIndex); 39744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // adjust our frame of reference to avoid a jump 39944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY += (y - oldY); 40044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX += (x - oldX); 40144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 40244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(index); 40344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.clear(); 40544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ok, we're good, we found a new pointer which is touching the active view 40744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return; 40844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 40944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if we made it this far, it means we didn't find a satisfactory new pointer :(, 41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so end the 41344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 41444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 41644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 41744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void handlePointerUp(MotionEvent ev) { 41844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 41944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 42044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int deltaY = (int) (newY - mInitialY); 42144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 42244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 42344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); 42444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 42544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 42644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.recycle(); 42744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = null; 42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 43044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN && 43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen !mGestureComplete) { 43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe down 43344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen showNext(); 43444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP && 43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen !mGestureComplete) { 43644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe up 43744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen showPrevious(); 43844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP && !mGestureComplete) { 43944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe up far enough, snap back down 44044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(mNumActiveViews - 2); 44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v != null) { 44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Compute the animation duration based on how far they pulled it up 44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) v.getLayoutParams(); 44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = (int) Math.round( 44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen lp.verticalOffset*1.0f/-mViewHeight*DEFAULT_ANIMATION_DURATION); 44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(MINIMUM_ANIMATION_DURATION, duration); 44744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 44844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Animate back down 44944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator slideDown = new PropertyAnimator(duration, lp, 45044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "verticalOffset", lp.verticalOffset, 0); 45144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen slideDown.start(); 45244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeIn = new PropertyAnimator(duration, v, 45344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "alpha",v.getAlpha(), 1.0f); 45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeIn.start(); 45544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 45644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN && !mGestureComplete) { 45744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe down far enough, snap back up 45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(mNumActiveViews - 1); 45944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v != null) { 46044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Compute the animation duration based on how far they pulled it down 46144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) v.getLayoutParams(); 46244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = (int) Math.round( 46344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen (1 - lp.verticalOffset*1.0f/-mViewHeight)*DEFAULT_ANIMATION_DURATION); 46444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(MINIMUM_ANIMATION_DURATION, duration); 46544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 46644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Animate back up 46744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator slideUp = new PropertyAnimator(duration, lp, 46844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "verticalOffset", lp.verticalOffset, -mViewHeight); 46944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen slideUp.start(); 47044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeOut = new PropertyAnimator(duration, v, 47144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen "alpha",v.getAlpha(), 0.0f); 47244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeOut.start(); 47344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 47444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 47544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 47644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 47744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mGestureComplete = true; 47844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 47944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mYOffset = 0; 48044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 48144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 48244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 48344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public void onRemoteAdapterConnected() { 48444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super.onRemoteAdapterConnected(); 48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setDisplayedChild(mIndex); 48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen} 488