blob: d57d5c68288371d845cb9050f599a092d952fb4d [file] [log] [blame]
Adam Cohenc99ff732011-01-16 16:11:41 -08001/* Copyright (C) 2010 The Android Open Source Project
Adam Cohen44729e32010-07-22 16:00:07 -07002 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package android.widget;
17
Adam Cohenc0b53be2010-12-17 19:23:41 -080018import java.lang.ref.WeakReference;
19
Chet Haasea18a86b2010-09-07 13:20:00 -070020import android.animation.ObjectAnimator;
Romain Guye5ebcb02010-10-15 13:57:28 -070021import android.animation.PropertyValuesHolder;
Adam Cohen44729e32010-07-22 16:00:07 -070022import android.content.Context;
Adam Cohen32a42f12010-08-11 19:34:30 -070023import android.graphics.Bitmap;
Adam Cohen839f4a52010-08-26 17:36:48 -070024import android.graphics.BlurMaskFilter;
Adam Cohen32a42f12010-08-11 19:34:30 -070025import android.graphics.Canvas;
26import android.graphics.Matrix;
27import android.graphics.Paint;
28import android.graphics.PorterDuff;
29import android.graphics.PorterDuffXfermode;
Adam Cohen44729e32010-07-22 16:00:07 -070030import android.graphics.Rect;
Adam Cohen9b073942010-08-19 16:49:52 -070031import android.graphics.RectF;
Adam Cohend51bbb52010-10-18 10:59:49 -070032import android.graphics.Region;
Adam Cohen839f4a52010-08-26 17:36:48 -070033import android.graphics.TableMaskFilter;
Adam Cohen44729e32010-07-22 16:00:07 -070034import android.util.AttributeSet;
35import android.util.Log;
36import android.view.MotionEvent;
37import android.view.VelocityTracker;
38import android.view.View;
39import android.view.ViewConfiguration;
40import android.view.ViewGroup;
Adam Cohenb04f7ad2010-08-15 13:22:42 -070041import android.view.animation.LinearInterpolator;
Adam Cohen44729e32010-07-22 16:00:07 -070042import android.widget.RemoteViews.RemoteView;
43
44@RemoteView
45/**
46 * A view that displays its children in a stack and allows users to discretely swipe
47 * through the children.
48 */
49public class StackView extends AdapterViewAnimator {
50 private final String TAG = "StackView";
51
52 /**
53 * Default animation parameters
54 */
Romain Guye5ebcb02010-10-15 13:57:28 -070055 private static final int DEFAULT_ANIMATION_DURATION = 400;
Adam Cohen026e1212010-12-13 12:29:17 -080056 private static final int FADE_IN_ANIMATION_DURATION = 800;
Romain Guye5ebcb02010-10-15 13:57:28 -070057 private static final int MINIMUM_ANIMATION_DURATION = 50;
Adam Cohenc0b53be2010-12-17 19:23:41 -080058 private static final int STACK_RELAYOUT_DURATION = 100;
Adam Cohen44729e32010-07-22 16:00:07 -070059
60 /**
Adam Cohen839f4a52010-08-26 17:36:48 -070061 * Parameters effecting the perspective visuals
62 */
Adam Cohen026e1212010-12-13 12:29:17 -080063 private static final float PERSPECTIVE_SHIFT_FACTOR_Y = 0.1f;
64 private static final float PERSPECTIVE_SHIFT_FACTOR_X = 0.1f;
65
Adam Cohen36f43902010-12-15 21:17:33 -080066 private float mPerspectiveShiftX;
67 private float mPerspectiveShiftY;
68 private float mNewPerspectiveShiftX;
69 private float mNewPerspectiveShiftY;
70
Romain Guye5ebcb02010-10-15 13:57:28 -070071 @SuppressWarnings({"FieldCanBeLocal"})
Adam Cohen78db1aa2011-01-25 12:24:23 -080072 private static final float PERSPECTIVE_SCALE_FACTOR = 0f;
Adam Cohen839f4a52010-08-26 17:36:48 -070073
74 /**
75 * Represent the two possible stack modes, one where items slide up, and the other
76 * where items slide down. The perspective is also inverted between these two modes.
77 */
78 private static final int ITEMS_SLIDE_UP = 0;
79 private static final int ITEMS_SLIDE_DOWN = 1;
80
81 /**
Adam Cohen44729e32010-07-22 16:00:07 -070082 * These specify the different gesture states
83 */
Romain Guy5b53f912010-08-16 18:24:33 -070084 private static final int GESTURE_NONE = 0;
85 private static final int GESTURE_SLIDE_UP = 1;
86 private static final int GESTURE_SLIDE_DOWN = 2;
Adam Cohen44729e32010-07-22 16:00:07 -070087
88 /**
89 * Specifies how far you need to swipe (up or down) before it
90 * will be consider a completed gesture when you lift your finger
91 */
Adam Cohena9238c82010-10-25 14:01:29 -070092 private static final float SWIPE_THRESHOLD_RATIO = 0.2f;
93
94 /**
95 * Specifies the total distance, relative to the size of the stack,
96 * that views will be slid, either up or down
97 */
Romain Guy5b53f912010-08-16 18:24:33 -070098 private static final float SLIDE_UP_RATIO = 0.7f;
Adam Cohen44729e32010-07-22 16:00:07 -070099
Adam Cohen44729e32010-07-22 16:00:07 -0700100 /**
101 * Sentinel value for no current active pointer.
102 * Used by {@link #mActivePointerId}.
103 */
104 private static final int INVALID_POINTER = -1;
105
106 /**
Adam Cohen839f4a52010-08-26 17:36:48 -0700107 * Number of active views in the stack. One fewer view is actually visible, as one is hidden.
108 */
109 private static final int NUM_ACTIVE_VIEWS = 5;
110
Adam Cohendfcdddd2010-09-10 14:38:40 -0700111 private static final int FRAME_PADDING = 4;
Adam Cohen839f4a52010-08-26 17:36:48 -0700112
Patrick Dubroye80202d2010-11-16 15:41:08 -0800113 private final Rect mTouchRect = new Rect();
114
Adam Cohen26e30bb2010-12-03 18:16:12 -0800115 private static final int MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE = 5000;
116
Adam Cohen839f4a52010-08-26 17:36:48 -0700117 /**
Adam Cohen44729e32010-07-22 16:00:07 -0700118 * These variables are all related to the current state of touch interaction
119 * with the stack
120 */
Adam Cohen44729e32010-07-22 16:00:07 -0700121 private float mInitialY;
122 private float mInitialX;
123 private int mActivePointerId;
Adam Cohen44729e32010-07-22 16:00:07 -0700124 private int mYVelocity = 0;
125 private int mSwipeGestureType = GESTURE_NONE;
Adam Cohendfcdddd2010-09-10 14:38:40 -0700126 private int mSlideAmount;
Adam Cohen44729e32010-07-22 16:00:07 -0700127 private int mSwipeThreshold;
128 private int mTouchSlop;
129 private int mMaximumVelocity;
130 private VelocityTracker mVelocityTracker;
Adam Cohen3352b682010-10-26 13:54:00 -0700131 private boolean mTransitionIsSetup = false;
Adam Cohen44729e32010-07-22 16:00:07 -0700132
Adam Cohen9b073942010-08-19 16:49:52 -0700133 private static HolographicHelper sHolographicHelper;
Adam Cohen32a42f12010-08-11 19:34:30 -0700134 private ImageView mHighlight;
Adam Cohen8baf5df2010-11-11 15:23:41 -0800135 private ImageView mClickFeedback;
136 private boolean mClickFeedbackIsValid = false;
Adam Cohen32a42f12010-08-11 19:34:30 -0700137 private StackSlider mStackSlider;
Adam Cohen44729e32010-07-22 16:00:07 -0700138 private boolean mFirstLayoutHappened = false;
Adam Cohen26e30bb2010-12-03 18:16:12 -0800139 private long mLastInteractionTime = 0;
Adam Cohen839f4a52010-08-26 17:36:48 -0700140 private int mStackMode;
Adam Cohendfcdddd2010-09-10 14:38:40 -0700141 private int mFramePadding;
Adam Cohen0ac116b2010-11-11 11:39:53 -0800142 private final Rect stackInvalidateRect = new Rect();
Adam Cohen44729e32010-07-22 16:00:07 -0700143
Adam Cohen44729e32010-07-22 16:00:07 -0700144 public StackView(Context context) {
145 super(context);
146 initStackView();
147 }
148
149 public StackView(Context context, AttributeSet attrs) {
150 super(context, attrs);
151 initStackView();
152 }
153
154 private void initStackView() {
Adam Cohen96d8d562010-10-24 11:12:18 -0700155 configureViewAnimator(NUM_ACTIVE_VIEWS, 1);
Adam Cohen44729e32010-07-22 16:00:07 -0700156 setStaticTransformationsEnabled(true);
157 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700158 mTouchSlop = configuration.getScaledTouchSlop();
Adam Cohen44729e32010-07-22 16:00:07 -0700159 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
160 mActivePointerId = INVALID_POINTER;
Adam Cohen32a42f12010-08-11 19:34:30 -0700161
162 mHighlight = new ImageView(getContext());
163 mHighlight.setLayoutParams(new LayoutParams(mHighlight));
164 addViewInLayout(mHighlight, -1, new LayoutParams(mHighlight));
Adam Cohen8baf5df2010-11-11 15:23:41 -0800165
166 mClickFeedback = new ImageView(getContext());
167 mClickFeedback.setLayoutParams(new LayoutParams(mClickFeedback));
168 addViewInLayout(mClickFeedback, -1, new LayoutParams(mClickFeedback));
169 mClickFeedback.setVisibility(INVISIBLE);
170
Adam Cohen32a42f12010-08-11 19:34:30 -0700171 mStackSlider = new StackSlider();
172
Adam Cohen9b073942010-08-19 16:49:52 -0700173 if (sHolographicHelper == null) {
Adam Cohendfcdddd2010-09-10 14:38:40 -0700174 sHolographicHelper = new HolographicHelper(mContext);
Adam Cohen32a42f12010-08-11 19:34:30 -0700175 }
Adam Cohen9b073942010-08-19 16:49:52 -0700176 setClipChildren(false);
177 setClipToPadding(false);
Adam Cohen1480fdd2010-08-25 17:24:53 -0700178
Adam Cohen839f4a52010-08-26 17:36:48 -0700179 // This sets the form of the StackView, which is currently to have the perspective-shifted
180 // views above the active view, and have items slide down when sliding out. The opposite is
181 // available by using ITEMS_SLIDE_UP.
182 mStackMode = ITEMS_SLIDE_DOWN;
183
Adam Cohen1480fdd2010-08-25 17:24:53 -0700184 // This is a flag to indicate the the stack is loading for the first time
185 mWhichChild = -1;
Adam Cohendfcdddd2010-09-10 14:38:40 -0700186
187 // Adjust the frame padding based on the density, since the highlight changes based
188 // on the density
189 final float density = mContext.getResources().getDisplayMetrics().density;
190 mFramePadding = (int) Math.ceil(density * FRAME_PADDING);
Adam Cohen44729e32010-07-22 16:00:07 -0700191 }
192
193 /**
194 * Animate the views between different relative indexes within the {@link AdapterViewAnimator}
195 */
Adam Cohen78db1aa2011-01-25 12:24:23 -0800196 void transformViewForTransition(int fromIndex, int toIndex, final View view, boolean animate) {
Adam Cohen69d66e02011-01-12 14:39:13 -0800197 ObjectAnimator alphaOa = null;
198 ObjectAnimator oldAlphaOa = null;
199
Adam Cohen78db1aa2011-01-25 12:24:23 -0800200 if (!animate) {
201 ((StackFrame) view).cancelSliderAnimator();
202 view.setRotationX(0f);
203 LayoutParams lp = (LayoutParams) view.getLayoutParams();
204 lp.setVerticalOffset(0);
205 lp.setHorizontalOffset(0);
Adam Cohen69d66e02011-01-12 14:39:13 -0800206 }
207
Adam Cohen50204582011-01-16 17:28:25 -0800208 if (fromIndex == -1 && toIndex == getNumActiveViews() -1) {
Adam Cohen44729e32010-07-22 16:00:07 -0700209 // Fade item in
210 if (view.getAlpha() == 1) {
211 view.setAlpha(0);
212 }
Adam Cohen50204582011-01-16 17:28:25 -0800213 transformViewAtIndex(toIndex, view, false);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700214 view.setVisibility(VISIBLE);
215
Adam Cohen78db1aa2011-01-25 12:24:23 -0800216 ((StackFrame) view).cancelAlphaAnimator();
217 if (animate) {
218 alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 1.0f);
219 alphaOa.setDuration(FADE_IN_ANIMATION_DURATION);
220 ((StackFrame) view).setAlphaAnimator(alphaOa);
221 alphaOa.start();
222 } else {
223 view.setAlpha(1.0f);
224 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700225 } else if (fromIndex == 0 && toIndex == 1) {
Adam Cohen44729e32010-07-22 16:00:07 -0700226 // Slide item in
Adam Cohen78db1aa2011-01-25 12:24:23 -0800227 ((StackFrame) view).cancelSliderAnimator();
Adam Cohen44729e32010-07-22 16:00:07 -0700228 view.setVisibility(VISIBLE);
Adam Cohen32a42f12010-08-11 19:34:30 -0700229
Adam Cohen839f4a52010-08-26 17:36:48 -0700230 int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity));
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700231 StackSlider animationSlider = new StackSlider(mStackSlider);
Adam Cohen69d66e02011-01-12 14:39:13 -0800232 animationSlider.setView(view);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800233
234 if (animate) {
235 PropertyValuesHolder slideInY = PropertyValuesHolder.ofFloat("YProgress", 0.0f);
236 PropertyValuesHolder slideInX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
237 ObjectAnimator slideIn = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
238 slideInX, slideInY);
239 slideIn.setDuration(duration);
240 slideIn.setInterpolator(new LinearInterpolator());
241 ((StackFrame) view).setSliderAnimator(slideIn);
242 slideIn.start();
243 } else {
244 animationSlider.setYProgress(0f);
245 animationSlider.setXProgress(0f);
246 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700247 } else if (fromIndex == 1 && toIndex == 0) {
Adam Cohen44729e32010-07-22 16:00:07 -0700248 // Slide item out
Adam Cohen78db1aa2011-01-25 12:24:23 -0800249 ((StackFrame) view).cancelSliderAnimator();
Adam Cohen839f4a52010-08-26 17:36:48 -0700250 int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity));
Adam Cohen44729e32010-07-22 16:00:07 -0700251
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700252 StackSlider animationSlider = new StackSlider(mStackSlider);
Adam Cohen69d66e02011-01-12 14:39:13 -0800253 animationSlider.setView(view);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800254 if (animate) {
255 PropertyValuesHolder slideOutY = PropertyValuesHolder.ofFloat("YProgress", 1.0f);
256 PropertyValuesHolder slideOutX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
257 ObjectAnimator slideOut = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
258 slideOutX, slideOutY);
259 slideOut.setDuration(duration);
260 slideOut.setInterpolator(new LinearInterpolator());
261 ((StackFrame) view).setSliderAnimator(slideOut);
262 slideOut.start();
263 } else {
264 animationSlider.setYProgress(1.0f);
265 animationSlider.setXProgress(0f);
266 }
Adam Cohen69d66e02011-01-12 14:39:13 -0800267 } else if (toIndex == 0) {
Adam Cohen44729e32010-07-22 16:00:07 -0700268 // Make sure this view that is "waiting in the wings" is invisible
269 view.setAlpha(0.0f);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700270 view.setVisibility(INVISIBLE);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800271 } else if ((fromIndex == 0 || fromIndex == 1) && toIndex > 1) {
Adam Cohen69d66e02011-01-12 14:39:13 -0800272 view.setVisibility(VISIBLE);
273 view.setAlpha(1.0f);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800274 view.setRotationX(0f);
275 LayoutParams lp = (LayoutParams) view.getLayoutParams();
276 lp.setVerticalOffset(0);
277 lp.setHorizontalOffset(0);
Adam Cohenc0b53be2010-12-17 19:23:41 -0800278 } else if (fromIndex == -1) {
279 view.setAlpha(1.0f);
280 view.setVisibility(VISIBLE);
Adam Cohen44729e32010-07-22 16:00:07 -0700281 } else if (toIndex == -1) {
282 // Fade item out
Adam Cohen78db1aa2011-01-25 12:24:23 -0800283 ((StackFrame) view).cancelAlphaAnimator();
284 if (animate) {
285 alphaOa = ObjectAnimator.ofFloat(view, "alpha", view.getAlpha(), 0.0f);
286 alphaOa.setDuration(STACK_RELAYOUT_DURATION);
287 ((StackFrame) view).setAlphaAnimator(alphaOa);
288 alphaOa.start();
289 } else {
290 view.setAlpha(0f);
291 }
Adam Cohen44729e32010-07-22 16:00:07 -0700292 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700293
294 // Implement the faked perspective
295 if (toIndex != -1) {
Adam Cohen78db1aa2011-01-25 12:24:23 -0800296 transformViewAtIndex(toIndex, view, animate);
Adam Cohenf04e2252010-09-09 18:41:20 -0700297 }
298 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700299
Adam Cohen36f43902010-12-15 21:17:33 -0800300 private void transformViewAtIndex(int index, final View view, boolean animate) {
301 final float maxPerspectiveShiftY = mPerspectiveShiftY;
302 final float maxPerspectiveShiftX = mPerspectiveShiftX;
Adam Cohen026e1212010-12-13 12:29:17 -0800303
Adam Cohenc99ff732011-01-16 16:11:41 -0800304 if (mStackMode == ITEMS_SLIDE_DOWN) {
305 index = mMaxNumActiveViews - index - 1;
306 if (index == mMaxNumActiveViews - 1) index--;
307 } else {
308 index--;
309 if (index < 0) index++;
310 }
Adam Cohen026e1212010-12-13 12:29:17 -0800311
312 float r = (index * 1.0f) / (mMaxNumActiveViews - 2);
313
Adam Cohen36f43902010-12-15 21:17:33 -0800314 final float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r);
Adam Cohen026e1212010-12-13 12:29:17 -0800315
Adam Cohenc99ff732011-01-16 16:11:41 -0800316 float perspectiveTranslationY = r * maxPerspectiveShiftY;
317 float scaleShiftCorrectionY = (scale - 1) *
Adam Cohen026e1212010-12-13 12:29:17 -0800318 (getMeasuredHeight() * (1 - PERSPECTIVE_SHIFT_FACTOR_Y) / 2.0f);
Adam Cohen36f43902010-12-15 21:17:33 -0800319 final float transY = perspectiveTranslationY + scaleShiftCorrectionY;
Adam Cohen026e1212010-12-13 12:29:17 -0800320
321 float perspectiveTranslationX = (1 - r) * maxPerspectiveShiftX;
322 float scaleShiftCorrectionX = (1 - scale) *
323 (getMeasuredWidth() * (1 - PERSPECTIVE_SHIFT_FACTOR_X) / 2.0f);
Adam Cohen36f43902010-12-15 21:17:33 -0800324 final float transX = perspectiveTranslationX + scaleShiftCorrectionX;
Adam Cohen026e1212010-12-13 12:29:17 -0800325
Adam Cohenc99ff732011-01-16 16:11:41 -0800326 // If this view is currently being animated for a certain position, we need to cancel
Adam Cohen69d66e02011-01-12 14:39:13 -0800327 // this animation so as not to interfere with the new transformation.
Adam Cohen78db1aa2011-01-25 12:24:23 -0800328 if (view instanceof StackFrame) {
329 ((StackFrame) view).cancelTransformAnimator();
Adam Cohen69d66e02011-01-12 14:39:13 -0800330 }
331
Adam Cohen36f43902010-12-15 21:17:33 -0800332 if (animate) {
333 PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", transX);
334 PropertyValuesHolder translationY = PropertyValuesHolder.ofFloat("translationY", transY);
335 PropertyValuesHolder scalePropX = PropertyValuesHolder.ofFloat("scaleX", scale);
336 PropertyValuesHolder scalePropY = PropertyValuesHolder.ofFloat("scaleY", scale);
Adam Cohen026e1212010-12-13 12:29:17 -0800337
Adam Cohen36f43902010-12-15 21:17:33 -0800338 ObjectAnimator oa = ObjectAnimator.ofPropertyValuesHolder(view, scalePropX, scalePropY,
339 translationY, translationX);
Adam Cohenc0b53be2010-12-17 19:23:41 -0800340 oa.setDuration(STACK_RELAYOUT_DURATION);
Adam Cohen78db1aa2011-01-25 12:24:23 -0800341 if (view instanceof StackFrame) {
342 ((StackFrame) view).setTransformAnimator(oa);
343 }
Adam Cohen36f43902010-12-15 21:17:33 -0800344 oa.start();
345 } else {
Adam Cohen36f43902010-12-15 21:17:33 -0800346 view.setTranslationX(transX);
347 view.setTranslationY(transY);
348 view.setScaleX(scale);
349 view.setScaleY(scale);
350 }
Adam Cohen026e1212010-12-13 12:29:17 -0800351 }
352
Adam Cohen3352b682010-10-26 13:54:00 -0700353 private void setupStackSlider(View v, int mode) {
354 mStackSlider.setMode(mode);
355 if (v != null) {
356 mHighlight.setImageBitmap(sHolographicHelper.createOutline(v));
357 mHighlight.setRotation(v.getRotation());
358 mHighlight.setTranslationY(v.getTranslationY());
Adam Cohen026e1212010-12-13 12:29:17 -0800359 mHighlight.setTranslationX(v.getTranslationX());
Adam Cohen3352b682010-10-26 13:54:00 -0700360 mHighlight.bringToFront();
361 v.bringToFront();
362 mStackSlider.setView(v);
363
364 v.setVisibility(VISIBLE);
365 }
366 }
367
Adam Cohen0e2de6d2011-01-19 17:16:34 -0800368 /**
369 * {@inheritDoc}
370 */
Adam Cohen3352b682010-10-26 13:54:00 -0700371 @Override
372 @android.view.RemotableViewMethod
373 public void showNext() {
Adam Cohen0ac116b2010-11-11 11:39:53 -0800374 if (mSwipeGestureType != GESTURE_NONE) return;
Adam Cohen3352b682010-10-26 13:54:00 -0700375 if (!mTransitionIsSetup) {
376 View v = getViewAtRelativeIndex(1);
377 if (v != null) {
378 setupStackSlider(v, StackSlider.NORMAL_MODE);
379 mStackSlider.setYProgress(0);
380 mStackSlider.setXProgress(0);
381 }
382 }
383 super.showNext();
384 }
385
Adam Cohen0e2de6d2011-01-19 17:16:34 -0800386 /**
387 * {@inheritDoc}
388 */
Adam Cohen3352b682010-10-26 13:54:00 -0700389 @Override
390 @android.view.RemotableViewMethod
391 public void showPrevious() {
Adam Cohen0ac116b2010-11-11 11:39:53 -0800392 if (mSwipeGestureType != GESTURE_NONE) return;
Adam Cohen3352b682010-10-26 13:54:00 -0700393 if (!mTransitionIsSetup) {
394 View v = getViewAtRelativeIndex(0);
395 if (v != null) {
396 setupStackSlider(v, StackSlider.NORMAL_MODE);
397 mStackSlider.setYProgress(1);
398 mStackSlider.setXProgress(0);
399 }
400 }
401 super.showPrevious();
402 }
403
Adam Cohen96d8d562010-10-24 11:12:18 -0700404 @Override
Adam Cohenef17dd42011-01-20 17:20:57 -0800405 void showOnly(int childIndex, boolean animate) {
406 super.showOnly(childIndex, animate);
Adam Cohen96d8d562010-10-24 11:12:18 -0700407
408 // Here we need to make sure that the z-order of the children is correct
Adam Cohen3352b682010-10-26 13:54:00 -0700409 for (int i = mCurrentWindowEnd; i >= mCurrentWindowStart; i--) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700410 int index = modulo(i, getWindowSize());
Adam Cohen0ac116b2010-11-11 11:39:53 -0800411 ViewAndIndex vi = mViewsMap.get(index);
412 if (vi != null) {
413 View v = mViewsMap.get(index).view;
414 if (v != null) v.bringToFront();
415 }
Adam Cohen96d8d562010-10-24 11:12:18 -0700416 }
Adam Cohen78db1aa2011-01-25 12:24:23 -0800417 if (mHighlight != null) {
418 mHighlight.bringToFront();
419 }
Adam Cohen3352b682010-10-26 13:54:00 -0700420 mTransitionIsSetup = false;
Adam Cohen8baf5df2010-11-11 15:23:41 -0800421 mClickFeedbackIsValid = false;
422 }
423
424 void updateClickFeedback() {
425 if (!mClickFeedbackIsValid) {
Adam Cohen9c295482010-11-18 15:19:48 -0800426 View v = getViewAtRelativeIndex(1);
Adam Cohen8baf5df2010-11-11 15:23:41 -0800427 if (v != null) {
428 mClickFeedback.setImageBitmap(sHolographicHelper.createOutline(v,
429 HolographicHelper.CLICK_FEEDBACK));
430 mClickFeedback.setTranslationX(v.getTranslationX());
431 mClickFeedback.setTranslationY(v.getTranslationY());
432 }
433 mClickFeedbackIsValid = true;
434 }
435 }
436
437 @Override
438 void showTapFeedback(View v) {
439 updateClickFeedback();
440 mClickFeedback.setVisibility(VISIBLE);
441 mClickFeedback.bringToFront();
442 invalidate();
443 }
444
445 @Override
446 void hideTapFeedback(View v) {
447 mClickFeedback.setVisibility(INVISIBLE);
448 invalidate();
Adam Cohen96d8d562010-10-24 11:12:18 -0700449 }
450
Adam Cohenf04e2252010-09-09 18:41:20 -0700451 private void updateChildTransforms() {
Adam Cohen96d8d562010-10-24 11:12:18 -0700452 for (int i = 0; i < getNumActiveViews(); i++) {
Adam Cohenf04e2252010-09-09 18:41:20 -0700453 View v = getViewAtRelativeIndex(i);
454 if (v != null) {
Adam Cohen36f43902010-12-15 21:17:33 -0800455 transformViewAtIndex(i, v, false);
Adam Cohenf04e2252010-09-09 18:41:20 -0700456 }
Adam Cohen839f4a52010-08-26 17:36:48 -0700457 }
Adam Cohen44729e32010-07-22 16:00:07 -0700458 }
459
Adam Cohen78db1aa2011-01-25 12:24:23 -0800460 private static class StackFrame extends FrameLayout {
461 WeakReference<ObjectAnimator> alphaAnimator;
462 WeakReference<ObjectAnimator> transformAnimator;
463 WeakReference<ObjectAnimator> sliderAnimator;
464
465 public StackFrame(Context context) {
466 super(context);
467 }
468
469 void setAlphaAnimator(ObjectAnimator oa) {
470 alphaAnimator = new WeakReference<ObjectAnimator>(oa);
471 }
472
473 void setTransformAnimator(ObjectAnimator oa) {
474 transformAnimator = new WeakReference<ObjectAnimator>(oa);
475 }
476
477 void setSliderAnimator(ObjectAnimator oa) {
478 sliderAnimator = new WeakReference<ObjectAnimator>(oa);
479 }
480
481 boolean cancelAlphaAnimator() {
482 if (alphaAnimator != null) {
483 ObjectAnimator oa = alphaAnimator.get();
484 if (oa != null) {
485 oa.cancel();
486 return true;
487 }
488 }
489 return false;
490 }
491
492 boolean cancelTransformAnimator() {
493 if (transformAnimator != null) {
494 ObjectAnimator oa = transformAnimator.get();
495 if (oa != null) {
496 oa.cancel();
497 return true;
498 }
499 }
500 return false;
501 }
502
503 boolean cancelSliderAnimator() {
504 if (sliderAnimator != null) {
505 ObjectAnimator oa = sliderAnimator.get();
506 if (oa != null) {
507 oa.cancel();
508 return true;
509 }
510 }
511 return false;
512 }
513 }
514
Adam Cohendfcdddd2010-09-10 14:38:40 -0700515 @Override
516 FrameLayout getFrameForChild() {
Adam Cohen78db1aa2011-01-25 12:24:23 -0800517 StackFrame fl = new StackFrame(mContext);
Adam Cohendfcdddd2010-09-10 14:38:40 -0700518 fl.setPadding(mFramePadding, mFramePadding, mFramePadding, mFramePadding);
519 return fl;
520 }
521
Adam Cohen44729e32010-07-22 16:00:07 -0700522 /**
523 * Apply any necessary tranforms for the child that is being added.
524 */
525 void applyTransformForChildAtIndex(View child, int relativeIndex) {
Adam Cohen44729e32010-07-22 16:00:07 -0700526 }
527
528 @Override
Adam Cohen9b073942010-08-19 16:49:52 -0700529 protected void dispatchDraw(Canvas canvas) {
Adam Cohen0ac116b2010-11-11 11:39:53 -0800530 canvas.getClipBounds(stackInvalidateRect);
Adam Cohend51bbb52010-10-18 10:59:49 -0700531 final int childCount = getChildCount();
532 for (int i = 0; i < childCount; i++) {
Adam Cohene86ff4d2011-01-21 17:46:11 -0800533 final View child = getChildAt(i);
534 LayoutParams lp = (LayoutParams) child.getLayoutParams();
535 if ((lp.horizontalOffset == 0 && lp.verticalOffset == 0) ||
536 child.getAlpha() == 0f || child.getVisibility() != VISIBLE) {
537 lp.resetInvalidateRect();
538 }
Adam Cohen0ac116b2010-11-11 11:39:53 -0800539 stackInvalidateRect.union(lp.getInvalidateRect());
Adam Cohen44729e32010-07-22 16:00:07 -0700540 }
Adam Cohend51bbb52010-10-18 10:59:49 -0700541 canvas.save(Canvas.CLIP_SAVE_FLAG);
Adam Cohen0ac116b2010-11-11 11:39:53 -0800542 canvas.clipRect(stackInvalidateRect, Region.Op.UNION);
Adam Cohend51bbb52010-10-18 10:59:49 -0700543 super.dispatchDraw(canvas);
544 canvas.restore();
Adam Cohen9b073942010-08-19 16:49:52 -0700545 }
Adam Cohen44729e32010-07-22 16:00:07 -0700546
Adam Cohen9b073942010-08-19 16:49:52 -0700547 private void onLayout() {
Adam Cohen44729e32010-07-22 16:00:07 -0700548 if (!mFirstLayoutHappened) {
Adam Cohendfcdddd2010-09-10 14:38:40 -0700549 mSlideAmount = Math.round(SLIDE_UP_RATIO * getMeasuredHeight());
Adam Cohendfcdddd2010-09-10 14:38:40 -0700550 mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mSlideAmount);
Adam Cohen44729e32010-07-22 16:00:07 -0700551 mFirstLayoutHappened = true;
Adam Cohen78db1aa2011-01-25 12:24:23 -0800552 post(new Runnable() {
553 public void run() {
554 updateChildTransforms();
555 }
556 });
Adam Cohen44729e32010-07-22 16:00:07 -0700557 }
Adam Cohen36f43902010-12-15 21:17:33 -0800558
559 if (Float.compare(mPerspectiveShiftY, mNewPerspectiveShiftY) != 0 ||
560 Float.compare(mPerspectiveShiftX, mNewPerspectiveShiftX) != 0) {
Adam Cohen78db1aa2011-01-25 12:24:23 -0800561
Adam Cohen36f43902010-12-15 21:17:33 -0800562 mPerspectiveShiftY = mNewPerspectiveShiftY;
563 mPerspectiveShiftX = mNewPerspectiveShiftX;
Adam Cohen78db1aa2011-01-25 12:24:23 -0800564
565 post(new Runnable() {
566 public void run() {
567 updateChildTransforms();
568 }
569 });
Adam Cohen36f43902010-12-15 21:17:33 -0800570 }
Adam Cohen44729e32010-07-22 16:00:07 -0700571 }
572
Adam Cohen0e2de6d2011-01-19 17:16:34 -0800573 /**
574 * {@inheritDoc}
575 */
Adam Cohen44729e32010-07-22 16:00:07 -0700576 @Override
577 public boolean onInterceptTouchEvent(MotionEvent ev) {
578 int action = ev.getAction();
579 switch(action & MotionEvent.ACTION_MASK) {
Adam Cohen44729e32010-07-22 16:00:07 -0700580 case MotionEvent.ACTION_DOWN: {
581 if (mActivePointerId == INVALID_POINTER) {
582 mInitialX = ev.getX();
583 mInitialY = ev.getY();
584 mActivePointerId = ev.getPointerId(0);
585 }
586 break;
587 }
588 case MotionEvent.ACTION_MOVE: {
589 int pointerIndex = ev.findPointerIndex(mActivePointerId);
590 if (pointerIndex == INVALID_POINTER) {
591 // no data for our primary pointer, this shouldn't happen, log it
592 Log.d(TAG, "Error: No data for our primary pointer.");
593 return false;
594 }
Adam Cohen44729e32010-07-22 16:00:07 -0700595 float newY = ev.getY(pointerIndex);
596 float deltaY = newY - mInitialY;
597
Adam Cohen32a42f12010-08-11 19:34:30 -0700598 beginGestureIfNeeded(deltaY);
Adam Cohen44729e32010-07-22 16:00:07 -0700599 break;
600 }
601 case MotionEvent.ACTION_POINTER_UP: {
602 onSecondaryPointerUp(ev);
603 break;
604 }
605 case MotionEvent.ACTION_UP:
606 case MotionEvent.ACTION_CANCEL: {
607 mActivePointerId = INVALID_POINTER;
608 mSwipeGestureType = GESTURE_NONE;
Adam Cohen44729e32010-07-22 16:00:07 -0700609 }
610 }
611
612 return mSwipeGestureType != GESTURE_NONE;
613 }
614
Adam Cohen32a42f12010-08-11 19:34:30 -0700615 private void beginGestureIfNeeded(float deltaY) {
616 if ((int) Math.abs(deltaY) > mTouchSlop && mSwipeGestureType == GESTURE_NONE) {
Adam Cohenc99ff732011-01-16 16:11:41 -0800617 final int swipeGestureType = deltaY < 0 ? GESTURE_SLIDE_UP : GESTURE_SLIDE_DOWN;
Adam Cohen32a42f12010-08-11 19:34:30 -0700618 cancelLongPress();
619 requestDisallowInterceptTouchEvent(true);
620
Adam Cohend51bbb52010-10-18 10:59:49 -0700621 if (mAdapter == null) return;
Adam Cohenef17dd42011-01-20 17:20:57 -0800622 final int adapterCount = getCount();
Adam Cohend51bbb52010-10-18 10:59:49 -0700623
Adam Cohen839f4a52010-08-26 17:36:48 -0700624 int activeIndex;
625 if (mStackMode == ITEMS_SLIDE_UP) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700626 activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
Adam Cohen839f4a52010-08-26 17:36:48 -0700627 } else {
Adam Cohen96d8d562010-10-24 11:12:18 -0700628 activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? 1 : 0;
Adam Cohen839f4a52010-08-26 17:36:48 -0700629 }
Adam Cohen32a42f12010-08-11 19:34:30 -0700630
Adam Cohenc99ff732011-01-16 16:11:41 -0800631 boolean endOfStack = mLoopViews && adapterCount == 1 &&
632 ((mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_UP) ||
633 (mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_DOWN));
634 boolean beginningOfStack = mLoopViews && adapterCount == 1 &&
635 ((mStackMode == ITEMS_SLIDE_DOWN && swipeGestureType == GESTURE_SLIDE_UP) ||
636 (mStackMode == ITEMS_SLIDE_UP && swipeGestureType == GESTURE_SLIDE_DOWN));
637
Adam Cohen3352b682010-10-26 13:54:00 -0700638 int stackMode;
Adam Cohenc99ff732011-01-16 16:11:41 -0800639 if (mLoopViews && !beginningOfStack && !endOfStack) {
Adam Cohen3352b682010-10-26 13:54:00 -0700640 stackMode = StackSlider.NORMAL_MODE;
Adam Cohenc99ff732011-01-16 16:11:41 -0800641 } else if (mCurrentWindowStartUnbounded + activeIndex == -1 || beginningOfStack) {
Adam Cohen96d8d562010-10-24 11:12:18 -0700642 activeIndex++;
Adam Cohen3352b682010-10-26 13:54:00 -0700643 stackMode = StackSlider.BEGINNING_OF_STACK_MODE;
Adam Cohenc99ff732011-01-16 16:11:41 -0800644 } else if (mCurrentWindowStartUnbounded + activeIndex == adapterCount - 1 || endOfStack) {
Adam Cohen3352b682010-10-26 13:54:00 -0700645 stackMode = StackSlider.END_OF_STACK_MODE;
Adam Cohen3d07af02010-08-18 17:46:23 -0700646 } else {
Adam Cohen3352b682010-10-26 13:54:00 -0700647 stackMode = StackSlider.NORMAL_MODE;
Adam Cohen32a42f12010-08-11 19:34:30 -0700648 }
Adam Cohen3d07af02010-08-18 17:46:23 -0700649
Adam Cohen3352b682010-10-26 13:54:00 -0700650 mTransitionIsSetup = stackMode == StackSlider.NORMAL_MODE;
651
Adam Cohen3d07af02010-08-18 17:46:23 -0700652 View v = getViewAtRelativeIndex(activeIndex);
653 if (v == null) return;
654
Adam Cohen3352b682010-10-26 13:54:00 -0700655 setupStackSlider(v, stackMode);
Adam Cohen3d07af02010-08-18 17:46:23 -0700656
657 // We only register this gesture if we've made it this far without a problem
658 mSwipeGestureType = swipeGestureType;
Adam Cohena32edd42010-10-26 10:35:01 -0700659 cancelHandleClick();
Adam Cohen32a42f12010-08-11 19:34:30 -0700660 }
661 }
662
Adam Cohen0e2de6d2011-01-19 17:16:34 -0800663 /**
664 * {@inheritDoc}
665 */
Adam Cohen44729e32010-07-22 16:00:07 -0700666 @Override
667 public boolean onTouchEvent(MotionEvent ev) {
Adam Cohena32edd42010-10-26 10:35:01 -0700668 super.onTouchEvent(ev);
669
Adam Cohen44729e32010-07-22 16:00:07 -0700670 int action = ev.getAction();
671 int pointerIndex = ev.findPointerIndex(mActivePointerId);
672 if (pointerIndex == INVALID_POINTER) {
673 // no data for our primary pointer, this shouldn't happen, log it
674 Log.d(TAG, "Error: No data for our primary pointer.");
675 return false;
676 }
677
678 float newY = ev.getY(pointerIndex);
Adam Cohen32a42f12010-08-11 19:34:30 -0700679 float newX = ev.getX(pointerIndex);
Adam Cohen44729e32010-07-22 16:00:07 -0700680 float deltaY = newY - mInitialY;
Adam Cohen32a42f12010-08-11 19:34:30 -0700681 float deltaX = newX - mInitialX;
Adam Cohen44729e32010-07-22 16:00:07 -0700682 if (mVelocityTracker == null) {
683 mVelocityTracker = VelocityTracker.obtain();
684 }
685 mVelocityTracker.addMovement(ev);
686
687 switch (action & MotionEvent.ACTION_MASK) {
688 case MotionEvent.ACTION_MOVE: {
Adam Cohen32a42f12010-08-11 19:34:30 -0700689 beginGestureIfNeeded(deltaY);
690
Adam Cohendfcdddd2010-09-10 14:38:40 -0700691 float rx = deltaX / (mSlideAmount * 1.0f);
Adam Cohen32a42f12010-08-11 19:34:30 -0700692 if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
Adam Cohendfcdddd2010-09-10 14:38:40 -0700693 float r = (deltaY - mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
Adam Cohen839f4a52010-08-26 17:36:48 -0700694 if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
Adam Cohen32a42f12010-08-11 19:34:30 -0700695 mStackSlider.setYProgress(1 - r);
696 mStackSlider.setXProgress(rx);
697 return true;
698 } else if (mSwipeGestureType == GESTURE_SLIDE_UP) {
Adam Cohendfcdddd2010-09-10 14:38:40 -0700699 float r = -(deltaY + mTouchSlop * 1.0f) / mSlideAmount * 1.0f;
Adam Cohen839f4a52010-08-26 17:36:48 -0700700 if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r;
Adam Cohen32a42f12010-08-11 19:34:30 -0700701 mStackSlider.setYProgress(r);
702 mStackSlider.setXProgress(rx);
703 return true;
Adam Cohen44729e32010-07-22 16:00:07 -0700704 }
Adam Cohen44729e32010-07-22 16:00:07 -0700705 break;
706 }
707 case MotionEvent.ACTION_UP: {
708 handlePointerUp(ev);
709 break;
710 }
711 case MotionEvent.ACTION_POINTER_UP: {
712 onSecondaryPointerUp(ev);
713 break;
714 }
715 case MotionEvent.ACTION_CANCEL: {
716 mActivePointerId = INVALID_POINTER;
Adam Cohen44729e32010-07-22 16:00:07 -0700717 mSwipeGestureType = GESTURE_NONE;
Adam Cohen44729e32010-07-22 16:00:07 -0700718 break;
719 }
720 }
721 return true;
722 }
723
Adam Cohen44729e32010-07-22 16:00:07 -0700724 private void onSecondaryPointerUp(MotionEvent ev) {
725 final int activePointerIndex = ev.getActionIndex();
726 final int pointerId = ev.getPointerId(activePointerIndex);
727 if (pointerId == mActivePointerId) {
728
Adam Cohen96d8d562010-10-24 11:12:18 -0700729 int activeViewIndex = (mSwipeGestureType == GESTURE_SLIDE_DOWN) ? 0 : 1;
Adam Cohen44729e32010-07-22 16:00:07 -0700730
731 View v = getViewAtRelativeIndex(activeViewIndex);
732 if (v == null) return;
733
734 // Our primary pointer has gone up -- let's see if we can find
735 // another pointer on the view. If so, then we should replace
736 // our primary pointer with this new pointer and adjust things
737 // so that the view doesn't jump
738 for (int index = 0; index < ev.getPointerCount(); index++) {
739 if (index != activePointerIndex) {
740
741 float x = ev.getX(index);
742 float y = ev.getY(index);
743
Patrick Dubroye80202d2010-11-16 15:41:08 -0800744 mTouchRect.set(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
745 if (mTouchRect.contains(Math.round(x), Math.round(y))) {
Adam Cohen44729e32010-07-22 16:00:07 -0700746 float oldX = ev.getX(activePointerIndex);
747 float oldY = ev.getY(activePointerIndex);
748
749 // adjust our frame of reference to avoid a jump
750 mInitialY += (y - oldY);
751 mInitialX += (x - oldX);
752
753 mActivePointerId = ev.getPointerId(index);
754 if (mVelocityTracker != null) {
755 mVelocityTracker.clear();
756 }
757 // ok, we're good, we found a new pointer which is touching the active view
758 return;
759 }
760 }
761 }
762 // if we made it this far, it means we didn't find a satisfactory new pointer :(,
Adam Cohen3d07af02010-08-18 17:46:23 -0700763 // so end the gesture
Adam Cohen44729e32010-07-22 16:00:07 -0700764 handlePointerUp(ev);
765 }
766 }
767
768 private void handlePointerUp(MotionEvent ev) {
769 int pointerIndex = ev.findPointerIndex(mActivePointerId);
770 float newY = ev.getY(pointerIndex);
771 int deltaY = (int) (newY - mInitialY);
Adam Cohen26e30bb2010-12-03 18:16:12 -0800772 mLastInteractionTime = System.currentTimeMillis();
Adam Cohen44729e32010-07-22 16:00:07 -0700773
Adam Cohen3d07af02010-08-18 17:46:23 -0700774 if (mVelocityTracker != null) {
775 mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
776 mYVelocity = (int) mVelocityTracker.getYVelocity(mActivePointerId);
777 }
Adam Cohen44729e32010-07-22 16:00:07 -0700778
779 if (mVelocityTracker != null) {
780 mVelocityTracker.recycle();
781 mVelocityTracker = null;
782 }
783
Adam Cohen3d07af02010-08-18 17:46:23 -0700784 if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN
785 && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
Adam Cohen0ac116b2010-11-11 11:39:53 -0800786 // We reset the gesture variable, because otherwise we will ignore showPrevious() /
787 // showNext();
788 mSwipeGestureType = GESTURE_NONE;
789
Adam Cohen44729e32010-07-22 16:00:07 -0700790 // Swipe threshold exceeded, swipe down
Adam Cohen839f4a52010-08-26 17:36:48 -0700791 if (mStackMode == ITEMS_SLIDE_UP) {
Adam Cohen839f4a52010-08-26 17:36:48 -0700792 showPrevious();
Adam Cohen96d8d562010-10-24 11:12:18 -0700793 } else {
794 showNext();
Adam Cohen839f4a52010-08-26 17:36:48 -0700795 }
Adam Cohen32a42f12010-08-11 19:34:30 -0700796 mHighlight.bringToFront();
Adam Cohen3d07af02010-08-18 17:46:23 -0700797 } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP
798 && mStackSlider.mMode == StackSlider.NORMAL_MODE) {
Adam Cohen0ac116b2010-11-11 11:39:53 -0800799 // We reset the gesture variable, because otherwise we will ignore showPrevious() /
800 // showNext();
801 mSwipeGestureType = GESTURE_NONE;
802
Adam Cohen44729e32010-07-22 16:00:07 -0700803 // Swipe threshold exceeded, swipe up
Adam Cohen839f4a52010-08-26 17:36:48 -0700804 if (mStackMode == ITEMS_SLIDE_UP) {
Adam Cohen839f4a52010-08-26 17:36:48 -0700805 showNext();
Adam Cohen96d8d562010-10-24 11:12:18 -0700806 } else {
807 showPrevious();
Adam Cohen839f4a52010-08-26 17:36:48 -0700808 }
809
Adam Cohen32a42f12010-08-11 19:34:30 -0700810 mHighlight.bringToFront();
Adam Cohen839f4a52010-08-26 17:36:48 -0700811 } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) {
Adam Cohen44729e32010-07-22 16:00:07 -0700812 // Didn't swipe up far enough, snap back down
Adam Cohen839f4a52010-08-26 17:36:48 -0700813 int duration;
814 float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0;
815 if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
816 duration = Math.round(mStackSlider.getDurationForNeutralPosition());
817 } else {
818 duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
819 }
Adam Cohen44729e32010-07-22 16:00:07 -0700820
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700821 StackSlider animationSlider = new StackSlider(mStackSlider);
Chet Haase2794eb32010-10-12 16:29:28 -0700822 PropertyValuesHolder snapBackY = PropertyValuesHolder.ofFloat("YProgress", finalYProgress);
823 PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
824 ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
Adam Cohen839f4a52010-08-26 17:36:48 -0700825 snapBackX, snapBackY);
Chet Haase2794eb32010-10-12 16:29:28 -0700826 pa.setDuration(duration);
Adam Cohen839f4a52010-08-26 17:36:48 -0700827 pa.setInterpolator(new LinearInterpolator());
828 pa.start();
Adam Cohen32a42f12010-08-11 19:34:30 -0700829 } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) {
Adam Cohen44729e32010-07-22 16:00:07 -0700830 // Didn't swipe down far enough, snap back up
Adam Cohen839f4a52010-08-26 17:36:48 -0700831 float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1;
832 int duration;
833 if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) {
834 duration = Math.round(mStackSlider.getDurationForNeutralPosition());
835 } else {
836 duration = Math.round(mStackSlider.getDurationForOffscreenPosition());
837 }
Adam Cohen3d07af02010-08-18 17:46:23 -0700838
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700839 StackSlider animationSlider = new StackSlider(mStackSlider);
Chet Haase2794eb32010-10-12 16:29:28 -0700840 PropertyValuesHolder snapBackY =
841 PropertyValuesHolder.ofFloat("YProgress",finalYProgress);
842 PropertyValuesHolder snapBackX = PropertyValuesHolder.ofFloat("XProgress", 0.0f);
843 ObjectAnimator pa = ObjectAnimator.ofPropertyValuesHolder(animationSlider,
Adam Cohen839f4a52010-08-26 17:36:48 -0700844 snapBackX, snapBackY);
Chet Haase2794eb32010-10-12 16:29:28 -0700845 pa.setDuration(duration);
Adam Cohen839f4a52010-08-26 17:36:48 -0700846 pa.start();
Adam Cohen44729e32010-07-22 16:00:07 -0700847 }
848
849 mActivePointerId = INVALID_POINTER;
Adam Cohen44729e32010-07-22 16:00:07 -0700850 mSwipeGestureType = GESTURE_NONE;
Adam Cohen32a42f12010-08-11 19:34:30 -0700851 }
852
853 private class StackSlider {
854 View mView;
855 float mYProgress;
856 float mXProgress;
857
Adam Cohen3d07af02010-08-18 17:46:23 -0700858 static final int NORMAL_MODE = 0;
859 static final int BEGINNING_OF_STACK_MODE = 1;
860 static final int END_OF_STACK_MODE = 2;
861
862 int mMode = NORMAL_MODE;
863
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700864 public StackSlider() {
865 }
866
867 public StackSlider(StackSlider copy) {
868 mView = copy.mView;
869 mYProgress = copy.mYProgress;
870 mXProgress = copy.mXProgress;
Adam Cohen3d07af02010-08-18 17:46:23 -0700871 mMode = copy.mMode;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700872 }
873
Adam Cohen32a42f12010-08-11 19:34:30 -0700874 private float cubic(float r) {
Adam Cohen839f4a52010-08-26 17:36:48 -0700875 return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f;
Adam Cohen32a42f12010-08-11 19:34:30 -0700876 }
877
878 private float highlightAlphaInterpolator(float r) {
879 float pivot = 0.4f;
880 if (r < pivot) {
Adam Cohen839f4a52010-08-26 17:36:48 -0700881 return 0.85f * cubic(r / pivot);
Adam Cohen32a42f12010-08-11 19:34:30 -0700882 } else {
Adam Cohen839f4a52010-08-26 17:36:48 -0700883 return 0.85f * cubic(1 - (r - pivot) / (1 - pivot));
Adam Cohen32a42f12010-08-11 19:34:30 -0700884 }
885 }
886
887 private float viewAlphaInterpolator(float r) {
888 float pivot = 0.3f;
889 if (r > pivot) {
Adam Cohen839f4a52010-08-26 17:36:48 -0700890 return (r - pivot) / (1 - pivot);
Adam Cohen32a42f12010-08-11 19:34:30 -0700891 } else {
892 return 0;
893 }
894 }
895
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700896 private float rotationInterpolator(float r) {
897 float pivot = 0.2f;
898 if (r < pivot) {
899 return 0;
900 } else {
Adam Cohen839f4a52010-08-26 17:36:48 -0700901 return (r - pivot) / (1 - pivot);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700902 }
903 }
904
Adam Cohen32a42f12010-08-11 19:34:30 -0700905 void setView(View v) {
906 mView = v;
907 }
908
909 public void setYProgress(float r) {
910 // enforce r between 0 and 1
911 r = Math.min(1.0f, r);
912 r = Math.max(0, r);
913
914 mYProgress = r;
Adam Cohena02fdf12010-11-03 13:27:40 -0700915 if (mView == null) return;
916
Adam Cohen32a42f12010-08-11 19:34:30 -0700917 final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
918 final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
919
Adam Cohen839f4a52010-08-26 17:36:48 -0700920 int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1;
921
Adam Cohen48867212011-01-09 13:49:40 -0800922 // We need to prevent any clipping issues which may arise by setting a layer type.
923 // This doesn't come for free however, so we only want to enable it when required.
924 if (Float.compare(0f, mYProgress) != 0 && Float.compare(1.0f, mYProgress) != 0) {
925 if (mView.getLayerType() == LAYER_TYPE_NONE) {
926 mView.setLayerType(LAYER_TYPE_HARDWARE, null);
927 }
928 } else {
929 if (mView.getLayerType() != LAYER_TYPE_NONE) {
930 mView.setLayerType(LAYER_TYPE_NONE, null);
931 }
932 }
933
Adam Cohen3d07af02010-08-18 17:46:23 -0700934 switch (mMode) {
935 case NORMAL_MODE:
Adam Cohendfcdddd2010-09-10 14:38:40 -0700936 viewLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
937 highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mSlideAmount));
Adam Cohen3d07af02010-08-18 17:46:23 -0700938 mHighlight.setAlpha(highlightAlphaInterpolator(r));
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700939
Adam Cohen839f4a52010-08-26 17:36:48 -0700940 float alpha = viewAlphaInterpolator(1 - r);
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700941
Adam Cohen3d07af02010-08-18 17:46:23 -0700942 // We make sure that views which can't be seen (have 0 alpha) are also invisible
943 // so that they don't interfere with click events.
944 if (mView.getAlpha() == 0 && alpha != 0 && mView.getVisibility() != VISIBLE) {
945 mView.setVisibility(VISIBLE);
946 } else if (alpha == 0 && mView.getAlpha() != 0
947 && mView.getVisibility() == VISIBLE) {
948 mView.setVisibility(INVISIBLE);
949 }
950
951 mView.setAlpha(alpha);
Adam Cohen839f4a52010-08-26 17:36:48 -0700952 mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
953 mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r));
Adam Cohen3d07af02010-08-18 17:46:23 -0700954 break;
Adam Cohen96d8d562010-10-24 11:12:18 -0700955 case END_OF_STACK_MODE:
Adam Cohen839f4a52010-08-26 17:36:48 -0700956 r = r * 0.2f;
Adam Cohendfcdddd2010-09-10 14:38:40 -0700957 viewLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
958 highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mSlideAmount));
Adam Cohen3d07af02010-08-18 17:46:23 -0700959 mHighlight.setAlpha(highlightAlphaInterpolator(r));
960 break;
Adam Cohen96d8d562010-10-24 11:12:18 -0700961 case BEGINNING_OF_STACK_MODE:
Adam Cohen839f4a52010-08-26 17:36:48 -0700962 r = (1-r) * 0.2f;
Adam Cohendfcdddd2010-09-10 14:38:40 -0700963 viewLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
964 highlightLp.setVerticalOffset(Math.round(stackDirection * r * mSlideAmount));
Adam Cohen3d07af02010-08-18 17:46:23 -0700965 mHighlight.setAlpha(highlightAlphaInterpolator(r));
966 break;
Adam Cohenb04f7ad2010-08-15 13:22:42 -0700967 }
Adam Cohen32a42f12010-08-11 19:34:30 -0700968 }
969
970 public void setXProgress(float r) {
971 // enforce r between 0 and 1
Adam Cohen3d07af02010-08-18 17:46:23 -0700972 r = Math.min(2.0f, r);
973 r = Math.max(-2.0f, r);
Adam Cohen32a42f12010-08-11 19:34:30 -0700974
975 mXProgress = r;
976
Adam Cohena02fdf12010-11-03 13:27:40 -0700977 if (mView == null) return;
Adam Cohen32a42f12010-08-11 19:34:30 -0700978 final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
979 final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams();
980
Adam Cohen3d07af02010-08-18 17:46:23 -0700981 r *= 0.2f;
Adam Cohendfcdddd2010-09-10 14:38:40 -0700982 viewLp.setHorizontalOffset(Math.round(r * mSlideAmount));
983 highlightLp.setHorizontalOffset(Math.round(r * mSlideAmount));
Adam Cohen32a42f12010-08-11 19:34:30 -0700984 }
985
Adam Cohen3d07af02010-08-18 17:46:23 -0700986 void setMode(int mode) {
987 mMode = mode;
988 }
989
990 float getDurationForNeutralPosition() {
Adam Cohen839f4a52010-08-26 17:36:48 -0700991 return getDuration(false, 0);
Adam Cohen3d07af02010-08-18 17:46:23 -0700992 }
993
994 float getDurationForOffscreenPosition() {
Adam Cohen839f4a52010-08-26 17:36:48 -0700995 return getDuration(true, 0);
Adam Cohen3d07af02010-08-18 17:46:23 -0700996 }
997
Adam Cohen839f4a52010-08-26 17:36:48 -0700998 float getDurationForNeutralPosition(float velocity) {
999 return getDuration(false, velocity);
1000 }
1001
1002 float getDurationForOffscreenPosition(float velocity) {
1003 return getDuration(true, velocity);
1004 }
1005
1006 private float getDuration(boolean invert, float velocity) {
Adam Cohen3d07af02010-08-18 17:46:23 -07001007 if (mView != null) {
1008 final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams();
1009
Adam Cohen839f4a52010-08-26 17:36:48 -07001010 float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) +
1011 Math.pow(viewLp.verticalOffset, 2));
Adam Cohendfcdddd2010-09-10 14:38:40 -07001012 float maxd = (float) Math.sqrt(Math.pow(mSlideAmount, 2) +
1013 Math.pow(0.4f * mSlideAmount, 2));
Adam Cohen839f4a52010-08-26 17:36:48 -07001014
1015 if (velocity == 0) {
1016 return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION;
1017 } else {
1018 float duration = invert ? d / Math.abs(velocity) :
1019 (maxd - d) / Math.abs(velocity);
1020 if (duration < MINIMUM_ANIMATION_DURATION ||
1021 duration > DEFAULT_ANIMATION_DURATION) {
1022 return getDuration(invert, 0);
1023 } else {
1024 return duration;
1025 }
1026 }
Adam Cohen3d07af02010-08-18 17:46:23 -07001027 }
1028 return 0;
1029 }
1030
Romain Guye5ebcb02010-10-15 13:57:28 -07001031 // Used for animations
1032 @SuppressWarnings({"UnusedDeclaration"})
Adam Cohen839f4a52010-08-26 17:36:48 -07001033 public float getYProgress() {
Adam Cohen32a42f12010-08-11 19:34:30 -07001034 return mYProgress;
1035 }
1036
Romain Guye5ebcb02010-10-15 13:57:28 -07001037 // Used for animations
1038 @SuppressWarnings({"UnusedDeclaration"})
Adam Cohen839f4a52010-08-26 17:36:48 -07001039 public float getXProgress() {
Adam Cohen32a42f12010-08-11 19:34:30 -07001040 return mXProgress;
1041 }
Adam Cohen44729e32010-07-22 16:00:07 -07001042 }
1043
Adam Cohen9b073942010-08-19 16:49:52 -07001044 LayoutParams createOrReuseLayoutParams(View v) {
1045 final ViewGroup.LayoutParams currentLp = v.getLayoutParams();
1046 if (currentLp instanceof LayoutParams) {
1047 LayoutParams lp = (LayoutParams) currentLp;
1048 lp.setHorizontalOffset(0);
1049 lp.setVerticalOffset(0);
Adam Cohen839f4a52010-08-26 17:36:48 -07001050 lp.width = 0;
1051 lp.width = 0;
Adam Cohen9b073942010-08-19 16:49:52 -07001052 return lp;
1053 }
1054 return new LayoutParams(v);
Adam Cohen32a42f12010-08-11 19:34:30 -07001055 }
1056
Adam Cohen9b073942010-08-19 16:49:52 -07001057 @Override
1058 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Adam Cohenef17dd42011-01-20 17:20:57 -08001059 checkForAndHandleDataChanged();
Adam Cohenb04f7ad2010-08-15 13:22:42 -07001060
Adam Cohen9b073942010-08-19 16:49:52 -07001061 final int childCount = getChildCount();
1062 for (int i = 0; i < childCount; i++) {
1063 final View child = getChildAt(i);
Adam Cohen32a42f12010-08-11 19:34:30 -07001064
Adam Cohen9b073942010-08-19 16:49:52 -07001065 int childRight = mPaddingLeft + child.getMeasuredWidth();
1066 int childBottom = mPaddingTop + child.getMeasuredHeight();
1067 LayoutParams lp = (LayoutParams) child.getLayoutParams();
Adam Cohen32a42f12010-08-11 19:34:30 -07001068
Adam Cohen9b073942010-08-19 16:49:52 -07001069 child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset,
1070 childRight + lp.horizontalOffset, childBottom + lp.verticalOffset);
1071
Adam Cohen9b073942010-08-19 16:49:52 -07001072 }
Adam Cohen9b073942010-08-19 16:49:52 -07001073 onLayout();
Adam Cohen32a42f12010-08-11 19:34:30 -07001074 }
1075
Adam Cohen26e30bb2010-12-03 18:16:12 -08001076 @Override
1077 public void advance() {
1078 long timeSinceLastInteraction = System.currentTimeMillis() - mLastInteractionTime;
Adam Cohenc99ff732011-01-16 16:11:41 -08001079
1080 if (mAdapter == null) return;
Adam Cohenef17dd42011-01-20 17:20:57 -08001081 final int adapterCount = getCount();
Adam Cohenc99ff732011-01-16 16:11:41 -08001082 if (adapterCount == 1 && mLoopViews) return;
1083
Adam Cohen26e30bb2010-12-03 18:16:12 -08001084 if (mSwipeGestureType == GESTURE_NONE &&
1085 timeSinceLastInteraction > MIN_TIME_BETWEEN_INTERACTION_AND_AUTOADVANCE) {
1086 showNext();
1087 }
1088 }
1089
Adam Cohen839f4a52010-08-26 17:36:48 -07001090 private void measureChildren() {
1091 final int count = getChildCount();
Adam Cohen36f43902010-12-15 21:17:33 -08001092
1093 final int measuredWidth = getMeasuredWidth();
1094 final int measuredHeight = getMeasuredHeight();
1095
1096 final int childWidth = Math.round(measuredWidth*(1-PERSPECTIVE_SHIFT_FACTOR_X))
Adam Cohen026e1212010-12-13 12:29:17 -08001097 - mPaddingLeft - mPaddingRight;
Adam Cohen36f43902010-12-15 21:17:33 -08001098 final int childHeight = Math.round(measuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR_Y))
Adam Cohen839f4a52010-08-26 17:36:48 -07001099 - mPaddingTop - mPaddingBottom;
1100
Adam Cohen36f43902010-12-15 21:17:33 -08001101 int maxWidth = 0;
1102 int maxHeight = 0;
1103
Adam Cohen839f4a52010-08-26 17:36:48 -07001104 for (int i = 0; i < count; i++) {
1105 final View child = getChildAt(i);
Adam Cohen36f43902010-12-15 21:17:33 -08001106 child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.AT_MOST),
1107 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST));
1108
1109 if (child != mHighlight && child != mClickFeedback) {
1110 final int childMeasuredWidth = child.getMeasuredWidth();
1111 final int childMeasuredHeight = child.getMeasuredHeight();
1112 if (childMeasuredWidth > maxWidth) {
1113 maxWidth = childMeasuredWidth;
1114 }
1115 if (childMeasuredHeight > maxHeight) {
1116 maxHeight = childMeasuredHeight;
1117 }
1118 }
1119 }
1120
1121 mNewPerspectiveShiftX = PERSPECTIVE_SHIFT_FACTOR_X * measuredWidth;
1122 mNewPerspectiveShiftY = PERSPECTIVE_SHIFT_FACTOR_Y * measuredHeight;
Adam Cohen78db1aa2011-01-25 12:24:23 -08001123 if (maxWidth > 0 && count > 0 && maxWidth < childWidth) {
Adam Cohen36f43902010-12-15 21:17:33 -08001124 mNewPerspectiveShiftX = measuredWidth - maxWidth;
1125 }
1126
Adam Cohen78db1aa2011-01-25 12:24:23 -08001127 if (maxHeight > 0 && count > 0 && maxHeight < childHeight) {
Adam Cohen36f43902010-12-15 21:17:33 -08001128 mNewPerspectiveShiftY = measuredHeight - maxHeight;
Adam Cohen839f4a52010-08-26 17:36:48 -07001129 }
1130 }
1131
1132 @Override
1133 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1134 int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
1135 int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
1136 final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
1137 final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
1138
1139 boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1);
1140
1141 // We need to deal with the case where our parent hasn't told us how
1142 // big we should be. In this case we should
Adam Cohen026e1212010-12-13 12:29:17 -08001143 float factorY = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_Y);
Adam Cohen839f4a52010-08-26 17:36:48 -07001144 if (heightSpecMode == MeasureSpec.UNSPECIFIED) {
1145 heightSpecSize = haveChildRefSize ?
Adam Cohen026e1212010-12-13 12:29:17 -08001146 Math.round(mReferenceChildHeight * (1 + factorY)) +
Adam Cohen839f4a52010-08-26 17:36:48 -07001147 mPaddingTop + mPaddingBottom : 0;
1148 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -08001149 if (haveChildRefSize) {
Adam Cohen026e1212010-12-13 12:29:17 -08001150 int height = Math.round(mReferenceChildHeight * (1 + factorY))
Dianne Hackborn189ee182010-12-02 21:48:53 -08001151 + mPaddingTop + mPaddingBottom;
1152 if (height <= heightSpecSize) {
1153 heightSpecSize = height;
1154 } else {
1155 heightSpecSize |= MEASURED_STATE_TOO_SMALL;
Adam Cohenef17dd42011-01-20 17:20:57 -08001156
Dianne Hackborn189ee182010-12-02 21:48:53 -08001157 }
1158 } else {
1159 heightSpecSize = 0;
1160 }
Adam Cohen839f4a52010-08-26 17:36:48 -07001161 }
1162
Adam Cohen026e1212010-12-13 12:29:17 -08001163 float factorX = 1/(1 - PERSPECTIVE_SHIFT_FACTOR_X);
Adam Cohen839f4a52010-08-26 17:36:48 -07001164 if (widthSpecMode == MeasureSpec.UNSPECIFIED) {
Adam Cohen026e1212010-12-13 12:29:17 -08001165 widthSpecSize = haveChildRefSize ?
1166 Math.round(mReferenceChildWidth * (1 + factorX)) +
1167 mPaddingLeft + mPaddingRight : 0;
Adam Cohen839f4a52010-08-26 17:36:48 -07001168 } else if (heightSpecMode == MeasureSpec.AT_MOST) {
Dianne Hackborn189ee182010-12-02 21:48:53 -08001169 if (haveChildRefSize) {
1170 int width = mReferenceChildWidth + mPaddingLeft + mPaddingRight;
1171 if (width <= widthSpecSize) {
1172 widthSpecSize = width;
1173 } else {
1174 widthSpecSize |= MEASURED_STATE_TOO_SMALL;
1175 }
1176 } else {
1177 widthSpecSize = 0;
1178 }
Adam Cohen839f4a52010-08-26 17:36:48 -07001179 }
Adam Cohen839f4a52010-08-26 17:36:48 -07001180 setMeasuredDimension(widthSpecSize, heightSpecSize);
1181 measureChildren();
1182 }
1183
Adam Cohen9b073942010-08-19 16:49:52 -07001184 class LayoutParams extends ViewGroup.LayoutParams {
1185 int horizontalOffset;
1186 int verticalOffset;
1187 View mView;
Adam Cohend51bbb52010-10-18 10:59:49 -07001188 private final Rect parentRect = new Rect();
1189 private final Rect invalidateRect = new Rect();
1190 private final RectF invalidateRectf = new RectF();
1191 private final Rect globalInvalidateRect = new Rect();
Adam Cohen32a42f12010-08-11 19:34:30 -07001192
Adam Cohen9b073942010-08-19 16:49:52 -07001193 LayoutParams(View view) {
1194 super(0, 0);
Adam Cohen839f4a52010-08-26 17:36:48 -07001195 width = 0;
1196 height = 0;
Adam Cohen9b073942010-08-19 16:49:52 -07001197 horizontalOffset = 0;
1198 verticalOffset = 0;
1199 mView = view;
1200 }
Adam Cohen32a42f12010-08-11 19:34:30 -07001201
Adam Cohen9b073942010-08-19 16:49:52 -07001202 LayoutParams(Context c, AttributeSet attrs) {
1203 super(c, attrs);
1204 horizontalOffset = 0;
1205 verticalOffset = 0;
Adam Cohen839f4a52010-08-26 17:36:48 -07001206 width = 0;
1207 height = 0;
Adam Cohen9b073942010-08-19 16:49:52 -07001208 }
Adam Cohen32a42f12010-08-11 19:34:30 -07001209
Adam Cohen9b073942010-08-19 16:49:52 -07001210 void invalidateGlobalRegion(View v, Rect r) {
Adam Cohend51bbb52010-10-18 10:59:49 -07001211 // We need to make a new rect here, so as not to modify the one passed
1212 globalInvalidateRect.set(r);
Adam Cohen9b073942010-08-19 16:49:52 -07001213 View p = v;
1214 if (!(v.getParent() != null && v.getParent() instanceof View)) return;
1215
Adam Cohen9b073942010-08-19 16:49:52 -07001216 boolean firstPass = true;
1217 parentRect.set(0, 0, 0, 0);
1218 int depth = 0;
Adam Cohenb7f4d032010-09-16 15:25:54 -07001219 while (p.getParent() != null && p.getParent() instanceof View
Adam Cohend51bbb52010-10-18 10:59:49 -07001220 && !parentRect.contains(globalInvalidateRect)) {
Adam Cohen9b073942010-08-19 16:49:52 -07001221 if (!firstPass) {
Adam Cohend51bbb52010-10-18 10:59:49 -07001222 globalInvalidateRect.offset(p.getLeft() - p.getScrollX(), p.getTop()
1223 - p.getScrollY());
Adam Cohen9b073942010-08-19 16:49:52 -07001224 depth++;
1225 }
1226 firstPass = false;
1227 p = (View) p.getParent();
Adam Cohenb7f4d032010-09-16 15:25:54 -07001228 parentRect.set(p.getScrollX(), p.getScrollY(),
1229 p.getWidth() + p.getScrollX(), p.getHeight() + p.getScrollY());
Adam Cohen839f4a52010-08-26 17:36:48 -07001230
Adam Cohen9b073942010-08-19 16:49:52 -07001231 }
1232
Adam Cohend51bbb52010-10-18 10:59:49 -07001233 p.invalidate(globalInvalidateRect.left, globalInvalidateRect.top,
1234 globalInvalidateRect.right, globalInvalidateRect.bottom);
Adam Cohen9b073942010-08-19 16:49:52 -07001235 }
1236
Adam Cohend51bbb52010-10-18 10:59:49 -07001237 Rect getInvalidateRect() {
1238 return invalidateRect;
1239 }
1240
1241 void resetInvalidateRect() {
Adam Cohen0ac116b2010-11-11 11:39:53 -08001242 invalidateRect.set(0, 0, 0, 0);
Adam Cohend51bbb52010-10-18 10:59:49 -07001243 }
1244
Chet Haasea18a86b2010-09-07 13:20:00 -07001245 // This is public so that ObjectAnimator can access it
Adam Cohen9b073942010-08-19 16:49:52 -07001246 public void setVerticalOffset(int newVerticalOffset) {
Adam Cohen0ac116b2010-11-11 11:39:53 -08001247 setOffsets(horizontalOffset, newVerticalOffset);
1248 }
1249
1250 public void setHorizontalOffset(int newHorizontalOffset) {
1251 setOffsets(newHorizontalOffset, verticalOffset);
1252 }
1253
1254 public void setOffsets(int newHorizontalOffset, int newVerticalOffset) {
1255 int horizontalOffsetDelta = newHorizontalOffset - horizontalOffset;
1256 horizontalOffset = newHorizontalOffset;
1257 int verticalOffsetDelta = newVerticalOffset - verticalOffset;
Adam Cohen9b073942010-08-19 16:49:52 -07001258 verticalOffset = newVerticalOffset;
1259
1260 if (mView != null) {
1261 mView.requestLayout();
Adam Cohen0ac116b2010-11-11 11:39:53 -08001262 int left = Math.min(mView.getLeft() + horizontalOffsetDelta, mView.getLeft());
1263 int right = Math.max(mView.getRight() + horizontalOffsetDelta, mView.getRight());
1264 int top = Math.min(mView.getTop() + verticalOffsetDelta, mView.getTop());
1265 int bottom = Math.max(mView.getBottom() + verticalOffsetDelta, mView.getBottom());
Adam Cohen9b073942010-08-19 16:49:52 -07001266
Adam Cohen0ac116b2010-11-11 11:39:53 -08001267 invalidateRectf.set(left, top, right, bottom);
Adam Cohen9b073942010-08-19 16:49:52 -07001268
1269 float xoffset = -invalidateRectf.left;
1270 float yoffset = -invalidateRectf.top;
1271 invalidateRectf.offset(xoffset, yoffset);
1272 mView.getMatrix().mapRect(invalidateRectf);
1273 invalidateRectf.offset(-xoffset, -yoffset);
1274
Adam Cohen0ac116b2010-11-11 11:39:53 -08001275 invalidateRect.set((int) Math.floor(invalidateRectf.left),
Adam Cohen9b073942010-08-19 16:49:52 -07001276 (int) Math.floor(invalidateRectf.top),
1277 (int) Math.ceil(invalidateRectf.right),
1278 (int) Math.ceil(invalidateRectf.bottom));
1279
1280 invalidateGlobalRegion(mView, invalidateRect);
1281 }
1282 }
1283 }
1284
1285 private static class HolographicHelper {
1286 private final Paint mHolographicPaint = new Paint();
1287 private final Paint mErasePaint = new Paint();
Adam Cohen839f4a52010-08-26 17:36:48 -07001288 private final Paint mBlurPaint = new Paint();
Adam Cohen8baf5df2010-11-11 15:23:41 -08001289 private static final int RES_OUT = 0;
1290 private static final int CLICK_FEEDBACK = 1;
1291 private float mDensity;
Patrick Dubroye80202d2010-11-16 15:41:08 -08001292 private BlurMaskFilter mSmallBlurMaskFilter;
1293 private BlurMaskFilter mLargeBlurMaskFilter;
1294 private final Canvas mCanvas = new Canvas();
1295 private final Canvas mMaskCanvas = new Canvas();
1296 private final int[] mTmpXY = new int[2];
1297 private final Matrix mIdentityMatrix = new Matrix();
Adam Cohen9b073942010-08-19 16:49:52 -07001298
Adam Cohendfcdddd2010-09-10 14:38:40 -07001299 HolographicHelper(Context context) {
Adam Cohen8baf5df2010-11-11 15:23:41 -08001300 mDensity = context.getResources().getDisplayMetrics().density;
Adam Cohendfcdddd2010-09-10 14:38:40 -07001301
Adam Cohen9b073942010-08-19 16:49:52 -07001302 mHolographicPaint.setFilterBitmap(true);
Adam Cohen839f4a52010-08-26 17:36:48 -07001303 mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30));
Adam Cohen9b073942010-08-19 16:49:52 -07001304 mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
1305 mErasePaint.setFilterBitmap(true);
Patrick Dubroye80202d2010-11-16 15:41:08 -08001306
1307 mSmallBlurMaskFilter = new BlurMaskFilter(2 * mDensity, BlurMaskFilter.Blur.NORMAL);
1308 mLargeBlurMaskFilter = new BlurMaskFilter(4 * mDensity, BlurMaskFilter.Blur.NORMAL);
Adam Cohen9b073942010-08-19 16:49:52 -07001309 }
1310
1311 Bitmap createOutline(View v) {
Adam Cohen8baf5df2010-11-11 15:23:41 -08001312 return createOutline(v, RES_OUT);
1313 }
1314
1315 Bitmap createOutline(View v, int type) {
1316 if (type == RES_OUT) {
1317 mHolographicPaint.setColor(0xff6699ff);
Patrick Dubroye80202d2010-11-16 15:41:08 -08001318 mBlurPaint.setMaskFilter(mSmallBlurMaskFilter);
Adam Cohen8baf5df2010-11-11 15:23:41 -08001319 } else if (type == CLICK_FEEDBACK) {
1320 mHolographicPaint.setColor(0x886699ff);
Patrick Dubroye80202d2010-11-16 15:41:08 -08001321 mBlurPaint.setMaskFilter(mLargeBlurMaskFilter);
Adam Cohen8baf5df2010-11-11 15:23:41 -08001322 }
1323
Adam Cohen9b073942010-08-19 16:49:52 -07001324 if (v.getMeasuredWidth() == 0 || v.getMeasuredHeight() == 0) {
1325 return null;
1326 }
1327
1328 Bitmap bitmap = Bitmap.createBitmap(v.getMeasuredWidth(), v.getMeasuredHeight(),
1329 Bitmap.Config.ARGB_8888);
Patrick Dubroye80202d2010-11-16 15:41:08 -08001330 mCanvas.setBitmap(bitmap);
Adam Cohen9b073942010-08-19 16:49:52 -07001331
1332 float rotationX = v.getRotationX();
Adam Cohen839f4a52010-08-26 17:36:48 -07001333 float rotation = v.getRotation();
1334 float translationY = v.getTranslationY();
Adam Cohen026e1212010-12-13 12:29:17 -08001335 float translationX = v.getTranslationX();
Adam Cohen9b073942010-08-19 16:49:52 -07001336 v.setRotationX(0);
Adam Cohen839f4a52010-08-26 17:36:48 -07001337 v.setRotation(0);
1338 v.setTranslationY(0);
Adam Cohen026e1212010-12-13 12:29:17 -08001339 v.setTranslationX(0);
Patrick Dubroye80202d2010-11-16 15:41:08 -08001340 v.draw(mCanvas);
Adam Cohen9b073942010-08-19 16:49:52 -07001341 v.setRotationX(rotationX);
Adam Cohen839f4a52010-08-26 17:36:48 -07001342 v.setRotation(rotation);
1343 v.setTranslationY(translationY);
Adam Cohen026e1212010-12-13 12:29:17 -08001344 v.setTranslationX(translationX);
Adam Cohen9b073942010-08-19 16:49:52 -07001345
Patrick Dubroye80202d2010-11-16 15:41:08 -08001346 drawOutline(mCanvas, bitmap);
Adam Cohen9b073942010-08-19 16:49:52 -07001347 return bitmap;
1348 }
1349
Adam Cohen9b073942010-08-19 16:49:52 -07001350 void drawOutline(Canvas dest, Bitmap src) {
Patrick Dubroye80202d2010-11-16 15:41:08 -08001351 final int[] xy = mTmpXY;
Adam Cohen839f4a52010-08-26 17:36:48 -07001352 Bitmap mask = src.extractAlpha(mBlurPaint, xy);
Patrick Dubroye80202d2010-11-16 15:41:08 -08001353 mMaskCanvas.setBitmap(mask);
1354 mMaskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint);
Adam Cohen9b073942010-08-19 16:49:52 -07001355 dest.drawColor(0, PorterDuff.Mode.CLEAR);
Patrick Dubroye80202d2010-11-16 15:41:08 -08001356 dest.setMatrix(mIdentityMatrix);
Adam Cohen839f4a52010-08-26 17:36:48 -07001357 dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint);
Adam Cohen9b073942010-08-19 16:49:52 -07001358 mask.recycle();
1359 }
Adam Cohen32a42f12010-08-11 19:34:30 -07001360 }
Adam Cohenef17dd42011-01-20 17:20:57 -08001361}