StackView.java revision 1480fddea874a42adb43b4bcdac6704e4c3e110b
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; 309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohenimport android.graphics.RectF; 3144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.AttributeSet; 3244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.util.Log; 3344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.MotionEvent; 3444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.VelocityTracker; 3544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.View; 3644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewConfiguration; 3744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.view.ViewGroup; 38b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohenimport android.view.animation.LinearInterpolator; 3944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenimport android.widget.RemoteViews.RemoteView; 4044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen@RemoteView 4244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen/** 4344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * A view that displays its children in a stack and allows users to discretely swipe 4444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * through the children. 4544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 4644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohenpublic class StackView extends AdapterViewAnimator { 4744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final String TAG = "StackView"; 4844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Default animation parameters 5144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen private final int DEFAULT_ANIMATION_DURATION = 400; 53b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen private final int MINIMUM_ANIMATION_DURATION = 50; 5444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 5544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 5644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * These specify the different gesture states 5744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 585b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_NONE = 0; 595b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_UP = 1; 605b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final int GESTURE_SLIDE_DOWN = 2; 6144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 6344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Specifies how far you need to swipe (up or down) before it 6444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * will be consider a completed gesture when you lift your finger 6544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 665b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SWIPE_THRESHOLD_RATIO = 0.35f; 675b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy private static final float SLIDE_UP_RATIO = 0.7f; 6844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 6944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>(); 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 929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static HolographicHelper sHolographicHelper; 9332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private ImageView mHighlight; 9432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private StackSlider mStackSlider; 9544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private boolean mFirstLayoutHappened = false; 969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private ViewGroup mAncestorContainingAllChildren = null; 979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private int mAncestorHeight = 0; 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() { 110b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen configureViewAnimator(4, 2, false); 11144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen setStaticTransformationsEnabled(true); 11244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 113b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mTouchSlop = configuration.getScaledTouchSlop(); 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 1229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (sHolographicHelper == null) { 1239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen sHolographicHelper = new HolographicHelper(); 12432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 1259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipChildren(false); 1269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen setClipToPadding(false); 1271480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen 1281480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // This is a flag to indicate the the stack is loading for the first time 1291480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen mWhichChild = -1; 13044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 13144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 13244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 13344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Animate the views between different relative indexes within the {@link AdapterViewAnimator} 13444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 13544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void animateViewForTransition(int fromIndex, int toIndex, View view) { 13644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (fromIndex == -1 && toIndex == 0) { 13744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item in 13844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (view.getAlpha() == 1) { 13944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0); 14044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 141b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(VISIBLE); 142b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 14344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, 14444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view, "alpha", view.getAlpha(), 1.0f); 14544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeIn.start(); 14644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) { 14744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item in 14844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setVisibility(VISIBLE); 14932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 15044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 1513d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int largestDuration = 1523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION); 15344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 15444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = largestDuration; 15544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mYVelocity != 0) { 15644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity); 15744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 15844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 15944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.min(duration, largestDuration); 16044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); 16144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 162b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 163b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator slideInY = new PropertyAnimator(duration, animationSlider, 16432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 0); 165b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen slideInY.setInterpolator(new LinearInterpolator()); 16632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideInY.start(); 167b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator slideInX = new PropertyAnimator(duration, animationSlider, 16832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 169b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen slideInX.setInterpolator(new LinearInterpolator()); 17032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideInX.start(); 17144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) { 17244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Slide item out 17344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 17444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 1753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int largestDuration = Math.round(mStackSlider.getDurationForOffscreenPosition()* 1763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen DEFAULT_ANIMATION_DURATION); 17744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int duration = largestDuration; 17844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mYVelocity != 0) { 17944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity); 18044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 18144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 18244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.min(duration, largestDuration); 18344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); 18444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 185b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 186b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator slideOutY = new PropertyAnimator(duration, animationSlider, 18732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 1); 188b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen slideOutY.setInterpolator(new LinearInterpolator()); 18932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideOutY.start(); 190b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator slideOutX = new PropertyAnimator(duration, animationSlider, 19132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 192b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen slideOutX.setInterpolator(new LinearInterpolator()); 19332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen slideOutX.start(); 19444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) { 19544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Make sure this view that is "waiting in the wings" is invisible 19644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view.setAlpha(0.0f); 197b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen view.setVisibility(INVISIBLE); 198b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen LayoutParams lp = (LayoutParams) view.getLayoutParams(); 199b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen lp.setVerticalOffset(-mViewHeight); 20044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } else if (toIndex == -1) { 20144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Fade item out 20244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, 20344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen view, "alpha", view.getAlpha(), 0); 20444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen fadeOut.start(); 20544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 20644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 20744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 20844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen /** 20944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen * Apply any necessary tranforms for the child that is being added. 21044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen */ 21144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen void applyTransformForChildAtIndex(View child, int relativeIndex) { 21244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mRotations.containsKey(child)) { 2135b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy float rotation = (float) (Math.random()*26 - 13); 21444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mRotations.put(child, rotation); 2159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.setRotation(rotation); 21644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 21744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 21844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Child has been removed 21944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (relativeIndex == -1) { 22044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mRotations.containsKey(child)) { 22144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mRotations.remove(child); 22244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 22344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 22444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 22544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 22644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 2279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void dispatchDraw(Canvas canvas) { 2289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super.dispatchDraw(canvas); 2299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 2309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 2319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // TODO: right now, this code walks up the hierarchy as far as needed and disables clipping 2329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // so that the stack's children can draw outside of the stack's bounds. This is fine within 2339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // the context of widgets in the launcher, but is destructive in general, as the clipping 2349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // values are not being reset. For this to be a full framework level widget, we will need 2359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // framework level support for drawing outside of a parent's bounds. 2369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private void disableParentalClipping() { 2379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mAncestorContainingAllChildren != null) { 2389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Log.v(TAG, "Disabling parental clipping."); 2399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen ViewGroup vg = this; 2409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen while (vg.getParent() != null && vg.getParent() instanceof ViewGroup) { 2419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (vg == mAncestorContainingAllChildren) break; 2429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen vg = (ViewGroup) vg.getParent(); 2439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen vg.setClipChildren(false); 2449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen vg.setClipToPadding(false); 24544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 24644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 2479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 24844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 2499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private void onLayout() { 25044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (!mFirstLayoutHappened) { 2515b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight()); 2525b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight); 25344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mFirstLayoutHappened = true; 25444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 25544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 25644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 25744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 25844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onInterceptTouchEvent(MotionEvent ev) { 25944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 26044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch(action & MotionEvent.ACTION_MASK) { 26144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_DOWN: { 26244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mActivePointerId == INVALID_POINTER) { 26344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX = ev.getX(); 26444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY = ev.getY(); 26544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(0); 26644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 26744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 26844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 26944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 27044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 27144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 27244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 27344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 27444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 27544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 27644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 27744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 27844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 27932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 28044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 28144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 28344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 28444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 28544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 28644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: 28744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 28844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 28944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 29044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 29144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 29244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 29344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return mSwipeGestureType != GESTURE_NONE; 29444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 29544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 29632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private void beginGestureIfNeeded(float deltaY) { 29732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) { 2983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN; 29932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen cancelLongPress(); 30032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen requestDisallowInterceptTouchEvent(true); 30132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 3023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int activeIndex = swipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1 30332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen : mNumActiveViews - 2; 30432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 3053d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mAdapter == null) return; 3063d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mCurrentWindowStartUnbounded + activeIndex == 0) { 3083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setMode(StackSlider.BEGINNING_OF_STACK_MODE); 3093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (mCurrentWindowStartUnbounded + activeIndex == mAdapter.getCount()) { 3103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen activeIndex--; 3113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setMode(StackSlider.END_OF_STACK_MODE); 3123d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else { 3133d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setMode(StackSlider.NORMAL_MODE); 31432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 3153d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3163d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen View v = getViewAtRelativeIndex(activeIndex); 3173d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (v == null) return; 3183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHighlight.setImageBitmap(sHolographicHelper.createOutline(v)); 3203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.bringToFront(); 3213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen v.bringToFront(); 3223d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mStackSlider.setView(v); 3233d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (swipeGestureType == GESTURE_SLIDE_DOWN) 3253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen v.setVisibility(VISIBLE); 3263d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 3273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We only register this gesture if we've made it this far without a problem 3283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mSwipeGestureType = swipeGestureType; 32932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 33032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 33132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 33244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 33344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public boolean onTouchEvent(MotionEvent ev) { 33444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int action = ev.getAction(); 33544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 33644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerIndex == INVALID_POINTER) { 33744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // no data for our primary pointer, this shouldn't happen, log it 33844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen Log.d(TAG, "Error: No data for our primary pointer."); 33944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return false; 34044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 34244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 34332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float newX = ev.getX(pointerIndex); 34444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float deltaY = newY - mInitialY; 34532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float deltaX = newX - mInitialX; 34644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker == null) { 34744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = VelocityTracker.obtain(); 34844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 34944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.addMovement(ev); 35044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 35144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen switch (action & MotionEvent.ACTION_MASK) { 35244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_MOVE: { 35332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen beginGestureIfNeeded(deltaY); 35432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 3553d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float rx = deltaX/(mViewHeight*1.0f); 35632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 35732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f; 35832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(1 - r); 35932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 36032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 36132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 36232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float r = -(deltaY + mTouchSlop*1.0f)/mViewHeight*1.0f; 36332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setYProgress(r); 36432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mStackSlider.setXProgress(rx); 36532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return true; 36644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 36844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 36944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_UP: { 37044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 37144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 37244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 37344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_POINTER_UP: { 37444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen onSecondaryPointerUp(ev); 37544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 37644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 37744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen case MotionEvent.ACTION_CANCEL: { 37844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 37944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 38044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen break; 38144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 38244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 38344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return true; 38444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 38544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 38644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private final Rect touchRect = new Rect(); 38744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void onSecondaryPointerUp(MotionEvent ev) { 38844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int activePointerIndex = ev.getActionIndex(); 38944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen final int pointerId = ev.getPointerId(activePointerIndex); 39044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (pointerId == mActivePointerId) { 39144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? mNumActiveViews - 1 39344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen : mNumActiveViews - 2; 39444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen View v = getViewAtRelativeIndex(activeViewIndex); 39644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (v == null) return; 39744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 39844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Our primary pointer has gone up -- let's see if we can find 39944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // another pointer on the view. If so, then we should replace 40044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // our primary pointer with this new pointer and adjust things 40144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // so that the view doesn't jump 40244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen for (int index = 0; index < ev.getPointerCount(); index++) { 40344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (index != activePointerIndex) { 40444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 40544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float x = ev.getX(index); 40644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float y = ev.getY(index); 40744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 40844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen touchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom()); 4095b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy if (touchRect.contains(Math.round(x), Math.round(y))) { 41044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldX = ev.getX(activePointerIndex); 41144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float oldY = ev.getY(activePointerIndex); 41244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 41344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // adjust our frame of reference to avoid a jump 41444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialY += (y - oldY); 41544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mInitialX += (x - oldX); 41644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 41744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = ev.getPointerId(index); 41844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 41944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.clear(); 42044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // ok, we're good, we found a new pointer which is touching the active view 42244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen return; 42344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 42644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // if we made it this far, it means we didn't find a satisfactory new pointer :(, 4273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so end the gesture 42844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen handlePointerUp(ev); 42944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 43144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 43244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen private void handlePointerUp(MotionEvent ev) { 43344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int pointerIndex = ev.findPointerIndex(mActivePointerId); 43444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen float newY = ev.getY(pointerIndex); 43544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen int deltaY = (int) (newY - mInitialY); 43644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4373d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mVelocityTracker != null) { 4383d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 4393d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); 4403d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 44144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 44244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen if (mVelocityTracker != null) { 44344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker.recycle(); 44444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mVelocityTracker = null; 44544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 44644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 4473d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN 4483d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 44944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe down 45044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen showNext(); 45132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 4523d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP 4533d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mStackSlider.mMode == StackSlider.NORMAL_MODE) { 45444729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Swipe threshold exceeded, swipe up 45544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen showPrevious(); 45632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mHighlight.bringToFront(); 45732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { 45844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe up far enough, snap back down 4593d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int duration = 4603d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION); 46132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 462b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 463b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider, 46432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 0); 465b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen snapBackY.setInterpolator(new LinearInterpolator()); 46632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackY.start(); 467b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider, 46832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 469b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen snapBackX.setInterpolator(new LinearInterpolator()); 47032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackX.start(); 47132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { 47244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen // Didn't swipe down far enough, snap back up 4733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int duration = Math.round(mStackSlider.getDurationForOffscreenPosition()* 4743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen DEFAULT_ANIMATION_DURATION); 4753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 476b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen StackSlider animationSlider = new StackSlider(mStackSlider); 477b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider, 47832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "YProgress", mStackSlider.getYProgress(), 1); 479b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen snapBackY.setInterpolator(new LinearInterpolator()); 48032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackY.start(); 481b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider, 48232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen "XProgress", mStackSlider.getXProgress(), 0); 483b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen snapBackX.setInterpolator(new LinearInterpolator()); 48432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen snapBackX.start(); 48544729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 48644729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 48744729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mActivePointerId = INVALID_POINTER; 48844729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen mSwipeGestureType = GESTURE_NONE; 48932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 49032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 49132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private class StackSlider { 49232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen View mView; 49332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mYProgress; 49432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float mXProgress; 49532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 4963d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int NORMAL_MODE = 0; 4973d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int BEGINNING_OF_STACK_MODE = 1; 4983d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen static final int END_OF_STACK_MODE = 2; 4993d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 5003d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen int mMode = NORMAL_MODE; 5013d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 502b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider() { 503b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 504b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 505b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen public StackSlider(StackSlider copy) { 506b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mView = copy.mView; 507b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mYProgress = copy.mYProgress; 508b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen mXProgress = copy.mXProgress; 5093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = copy.mMode; 510b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 511b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 51232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float cubic(float r) { 51332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return (float) (Math.pow(2*r-1, 3) + 1)/2.0f; 51432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 51532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 51632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float highlightAlphaInterpolator(float r) { 51732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.4f; 51832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r < pivot) { 51932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0.85f*cubic(r/pivot); 52032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 52132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0.85f*cubic(1 - (r-pivot)/(1-pivot)); 52232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 52332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 52432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 52532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen private float viewAlphaInterpolator(float r) { 52632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float pivot = 0.3f; 52732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen if (r > pivot) { 52832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return (r - pivot)/(1 - pivot); 52932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } else { 53032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return 0; 53132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 53232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 53332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 534b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen private float rotationInterpolator(float r) { 535b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen float pivot = 0.2f; 536b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen if (r < pivot) { 537b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen return 0; 538b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } else { 539b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen return (r-pivot)/(1-pivot); 540b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 541b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 542b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 54332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen void setView(View v) { 54432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mView = v; 54532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 54632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 54732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setYProgress(float r) { 54832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 54932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.min(1.0f, r); 55032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen r = Math.max(0, r); 55132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 55232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mYProgress = r; 55332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 55432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 55532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 5563d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen switch (mMode) { 5573d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case NORMAL_MODE: 5583d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen viewLp.setVerticalOffset(Math.round(-r*mViewHeight)); 5593d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen highlightLp.setVerticalOffset(Math.round(-r*mViewHeight)); 5603d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 5613d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 5623d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float alpha = viewAlphaInterpolator(1-r); 5633d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 5643d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // We make sure that views which can't be seen (have 0 alpha) are also invisible 5653d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen // so that they don't interfere with click events. 5663d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) { 5673d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(VISIBLE); 5683d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } else if (alpha == 0 && mView.getAlpha() != 0 5693d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen && mView.getVisibility() == VISIBLE) { 5703d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setVisibility(INVISIBLE); 5713d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 572b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 5733d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setAlpha(alpha); 5743d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mView.setRotationX(90.0f*rotationInterpolator(r)); 5753d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setRotationX(90.0f*rotationInterpolator(r)); 5763d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 5773d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case BEGINNING_OF_STACK_MODE: 5783d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = r*0.2f; 5793d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen viewLp.setVerticalOffset(Math.round(-r*mViewHeight)); 5803d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen highlightLp.setVerticalOffset(Math.round(-r*mViewHeight)); 5813d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 5823d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 5833d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen case END_OF_STACK_MODE: 5843d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = (1-r)*0.2f; 5853d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen viewLp.setVerticalOffset(Math.round(r*mViewHeight)); 5863d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen highlightLp.setVerticalOffset(Math.round(r*mViewHeight)); 5873d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mHighlight.setAlpha(highlightAlphaInterpolator(r)); 5883d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen break; 589b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 59032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 59132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 59232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen public void setXProgress(float r) { 59332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen // enforce r between 0 and 1 5943d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.min(2.0f, r); 5953d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r = Math.max(-2.0f, r); 59632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 59732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen mXProgress = r; 59832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 59932a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 60032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); 60132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6023d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen r *= 0.2f; 6035b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy viewLp.setHorizontalOffset(Math.round(r*mViewHeight)); 6045b53f9186e7812c93bc578d18e92cb123481fcbcRomain Guy highlightLp.setHorizontalOffset(Math.round(r*mViewHeight)); 60532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 60632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6073d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen void setMode(int mode) { 6083d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen mMode = mode; 6093d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 6103d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6113d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForNeutralPosition() { 6123d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return getDuration(false); 6133d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 6143d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6153d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float getDurationForOffscreenPosition() { 6163d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return getDuration(mMode == END_OF_STACK_MODE ? false : true); 6173d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 6183d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6193d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen private float getDuration(boolean invert) { 6203d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen if (mView != null) { 6213d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); 6223d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 6233d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset,2) + 6243d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen Math.pow(viewLp.verticalOffset,2)); 6253d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen float maxd = (float) Math.sqrt(Math.pow(mViewHeight, 2) + 6263d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen Math.pow(0.4f*mViewHeight, 2)); 6273d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return invert ? (1-d/maxd) : d/maxd; 6283d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 6293d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen return 0; 6303d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen } 6313d07af03421f4727ef7e97c5c19e6ade50b19060Adam Cohen 63232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float getYProgress() { 63332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mYProgress; 63432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 63532a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 63632a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen float getXProgress() { 63732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen return mXProgress; 63832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 63944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 64044729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen 64144729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen @Override 64244729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen public void onRemoteAdapterConnected() { 64344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen super.onRemoteAdapterConnected(); 6441480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen // On first run, we want to set the stack to the end. 6451480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen if (mAdapter != null && mWhichChild == -1) { 6461480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen mWhichChild = mAdapter.getCount() - 1; 6471480fddea874a42adb43b4bcdac6704e4c3e110bAdam Cohen } 648b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen setDisplayedChild(mWhichChild); 64944729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen } 65032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams createOrReuseLayoutParams(View v) { 6529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final ViewGroup.LayoutParams currentLp = v.getLayoutParams(); 6539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (currentLp instanceof LayoutParams) { 6549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) currentLp; 6559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setHorizontalOffset(0); 6569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen lp.setVerticalOffset(0); 6579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return lp; 6589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 6599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return new LayoutParams(v); 66032a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 66132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 6629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen @Override 6639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 6649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean dataChanged = mDataChanged; 6659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (dataChanged) { 6669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen handleDataChanged(); 6679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // if the data changes, mWhichChild might be out of the bounds of the adapter 6699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // in this case, we reset mWhichChild to the beginning 6709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mWhichChild >= mAdapter.getCount()) 6719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mWhichChild = 0; 6729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen showOnly(mWhichChild, true, true); 6749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 6759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final int childCount = getChildCount(); 6779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen for (int i = 0; i < childCount; i++) { 6789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final View child = getChildAt(i); 6799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childRight = mPaddingLeft + child.getMeasuredWidth(); 6819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int childBottom = mPaddingTop + child.getMeasuredHeight(); 6829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams lp = (LayoutParams) child.getLayoutParams(); 6839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, 6859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); 6869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen //TODO: temp until fix in View 6889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.setPivotX(child.getMeasuredWidth()/2); 6899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen child.setPivotY(child.getMeasuredHeight()/2); 6909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 6919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mDataChanged = false; 6939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen onLayout(); 6949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 6959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 6969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen class LayoutParams extends ViewGroup.LayoutParams { 6979b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int horizontalOffset; 6989b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int verticalOffset; 6999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View mView; 7009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(View view) { 7029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(0, 0); 7039b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 7049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 7059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView = view; 7069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen LayoutParams(Context c, AttributeSet attrs) { 7099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen super(c, attrs); 7109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = 0; 7119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = 0; 712b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen } 713b04f7ad90b7d5d5e0998e3b56960004cf56e6e8fAdam Cohen 7149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private Rect parentRect = new Rect(); 7159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void invalidateGlobalRegion(View v, Rect r) { 7169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View p = v; 7179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!(v.getParent() != null && v.getParent() instanceof View)) return; 7189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen View gp = (View) v.getParent(); 7209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen boolean firstPass = true; 7219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen parentRect.set(0, 0, 0, 0); 7229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int depth = 0; 7239b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen while (gp.getParent() != null && gp.getParent() instanceof View 7249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen && !parentRect.contains(r)) { 7259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (!firstPass) { 7269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen r.offset(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY()); 7279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen depth++; 7289b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen firstPass = false; 7309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p = (View) p.getParent(); 7319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen gp = (View) p.getParent(); 7329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen parentRect.set(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY(), 7339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p.getRight() - gp.getScrollX(), p.getBottom() - gp.getScrollY()); 7349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (depth > mAncestorHeight) { 7379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mAncestorContainingAllChildren = (ViewGroup) p; 7389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mAncestorHeight = depth; 7399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen disableParentalClipping(); 7409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen p.invalidate(r.left, r.top, r.right, r.bottom); 7439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 74432a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private Rect invalidateRect = new Rect(); 7469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private RectF invalidateRectf = new RectF(); 7479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen // This is public so that PropertyAnimator can access it 7489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setVerticalOffset(int newVerticalOffset) { 7499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int offsetDelta = newVerticalOffset - verticalOffset; 7509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen verticalOffset = newVerticalOffset; 75132a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7529b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mView != null) { 7539b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.requestLayout(); 7549b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int top = Math.min(mView.getTop() + offsetDelta, mView.getTop()); 7559b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int bottom = Math.max(mView.getBottom() + offsetDelta, mView.getBottom()); 7569b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7579b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.set(mView.getLeft(), top, mView.getRight(), bottom); 7589b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7599b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xoffset = -invalidateRectf.left; 7609b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yoffset = -invalidateRectf.top; 7619b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(xoffset, yoffset); 7629b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.getMatrix().mapRect(invalidateRectf); 7639b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(-xoffset, -yoffset); 7649b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRect.set((int) Math.floor(invalidateRectf.left), 7659b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.floor(invalidateRectf.top), 7669b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.right), 7679b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.bottom)); 7689b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7699b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateGlobalRegion(mView, invalidateRect); 7709b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7719b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7729b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7739b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen public void setHorizontalOffset(int newHorizontalOffset) { 7749b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int offsetDelta = newHorizontalOffset - horizontalOffset; 7759b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen horizontalOffset = newHorizontalOffset; 7769b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7779b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (mView != null) { 7789b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.requestLayout(); 7799b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int left = Math.min(mView.getLeft() + offsetDelta, mView.getLeft()); 7809b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen int right = Math.max(mView.getRight() + offsetDelta, mView.getRight()); 7819b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.set(left, mView.getTop(), right, mView.getBottom()); 7829b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7839b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xoffset = -invalidateRectf.left; 7849b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yoffset = -invalidateRectf.top; 7859b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(xoffset, yoffset); 7869b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mView.getMatrix().mapRect(invalidateRectf); 7879b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRectf.offset(-xoffset, -yoffset); 7889b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7899b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateRect.set((int) Math.floor(invalidateRectf.left), 7909b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.floor(invalidateRectf.top), 7919b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.right), 7929b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen (int) Math.ceil(invalidateRectf.bottom)); 7939b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 7949b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen invalidateGlobalRegion(mView, invalidateRect); 7959b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 7969b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 79732a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 79832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 7999b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private static class HolographicHelper { 8009b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mHolographicPaint = new Paint(); 8019b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final Paint mErasePaint = new Paint(); 8029b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen private final float STROKE_WIDTH = 3.0f; 80332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 8049b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen HolographicHelper() { 8059b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen initializePaints(); 8069b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8079b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8089b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void initializePaints() { 8099b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHolographicPaint.setColor(0xff6699ff); 8109b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mHolographicPaint.setFilterBitmap(true); 8119b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); 8129b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mErasePaint.setFilterBitmap(true); 8139b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8149b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8159b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap createOutline(View v) { 8169b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) { 8179b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return null; 8189b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8199b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8209b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(), 8219b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap.Config.ARGB_8888); 8229b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Canvas canvas = new Canvas(bitmap); 82332a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 8249b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float rotationX = v.getRotationX(); 8259b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(0); 8269b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen canvas.concat(v.getMatrix()); 8279b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.draw(canvas); 82832a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen 8299b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen v.setRotationX(rotationX); 8309b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8319b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen drawOutline(canvas, bitmap); 8329b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen return bitmap; 8339b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 8349b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8359b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final Matrix id = new Matrix(); 8369b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen final Matrix scaleMatrix = new Matrix(); 8379b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen void drawOutline(Canvas dest, Bitmap src) { 8389b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen Bitmap mask = src.extractAlpha(); 8399b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8409b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.drawColor(0, PorterDuff.Mode.CLEAR); 8419b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8429b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float xScale = STROKE_WIDTH*2/(dest.getWidth()); 8439b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen float yScale = STROKE_WIDTH*2/(dest.getHeight()); 8449b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen 8459b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen scaleMatrix.reset(); 8469b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen scaleMatrix.preScale(1+xScale, 1+yScale, dest.getWidth()/2, dest.getHeight()/2); 8479b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.setMatrix(id); 8489b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.drawBitmap(mask, scaleMatrix, mHolographicPaint); 8499b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen dest.drawBitmap(mask, id, mErasePaint); 8509b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen mask.recycle(); 8519b073948cfb84c0dd04f8a94ee1f7f263f027c83Adam Cohen } 85232a42f1587db77b958d62c3de4f2734eb0a3b965Adam Cohen } 85344729e3d1c01265858eec566c7b7c676c46a7916Adam Cohen} 854