blob: 53c17a5e7adca11e48b08d422b57c766ee9c5fe2 [file] [log] [blame]
Jim Millerdcb3d842012-08-23 19:18:12 -07001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Jim Miller5ecd8112013-01-09 18:50:26 -080017package com.android.keyguard;
Jim Millerdcb3d842012-08-23 19:18:12 -070018
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Jim Millerd6523da2012-10-21 16:47:02 -070021import android.animation.AnimatorSet;
Jim Millerdcb3d842012-08-23 19:18:12 -070022import android.animation.ObjectAnimator;
Jim Millerd6523da2012-10-21 16:47:02 -070023import android.animation.TimeInterpolator;
Jim Millerdcb3d842012-08-23 19:18:12 -070024import android.animation.ValueAnimator;
Jim Millerd6523da2012-10-21 16:47:02 -070025import android.animation.ValueAnimator.AnimatorUpdateListener;
Jim Millerdcb3d842012-08-23 19:18:12 -070026import android.content.Context;
Winson Chung48275d22012-11-05 10:56:31 -080027import android.content.res.Resources;
Jim Millerdcb3d842012-08-23 19:18:12 -070028import android.content.res.TypedArray;
29import android.graphics.Canvas;
Jim Millerd6523da2012-10-21 16:47:02 -070030import android.graphics.Matrix;
31import android.graphics.PointF;
Jim Millerdcb3d842012-08-23 19:18:12 -070032import android.graphics.Rect;
33import android.os.Bundle;
34import android.os.Parcel;
35import android.os.Parcelable;
36import android.util.AttributeSet;
Winson Chung6cf53bb2012-11-05 17:55:42 -080037import android.util.DisplayMetrics;
Jim Millerdcb3d842012-08-23 19:18:12 -070038import android.util.Log;
39import android.view.InputDevice;
40import android.view.KeyEvent;
41import android.view.MotionEvent;
42import android.view.VelocityTracker;
43import android.view.View;
44import android.view.ViewConfiguration;
45import android.view.ViewGroup;
46import android.view.ViewParent;
Jim Millerafef5b22013-10-13 16:49:43 -070047import android.view.ViewPropertyAnimator;
Jim Millerdcb3d842012-08-23 19:18:12 -070048import android.view.accessibility.AccessibilityEvent;
49import android.view.accessibility.AccessibilityManager;
50import android.view.accessibility.AccessibilityNodeInfo;
Jim Millerc162dd02013-09-25 18:57:43 -070051import android.view.animation.AccelerateInterpolator;
Jim Millerd6523da2012-10-21 16:47:02 -070052import android.view.animation.AnimationUtils;
53import android.view.animation.DecelerateInterpolator;
Jim Millerdcb3d842012-08-23 19:18:12 -070054import android.view.animation.Interpolator;
Winson Chungf3b9ec82012-11-01 14:48:51 -070055import android.view.animation.LinearInterpolator;
Jim Millerdcb3d842012-08-23 19:18:12 -070056import android.widget.Scroller;
57
Jim Millerdcb3d842012-08-23 19:18:12 -070058import java.util.ArrayList;
59
60/**
61 * An abstraction of the original Workspace which supports browsing through a
62 * sequential list of "pages"
63 */
Michael Jurka1254f2f2012-10-25 11:44:31 -070064public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
Jim Millerc162dd02013-09-25 18:57:43 -070065 private static final int WARP_SNAP_DURATION = 160;
Jim Millerdcb3d842012-08-23 19:18:12 -070066 private static final String TAG = "WidgetPagedView";
67 private static final boolean DEBUG = false;
Jim Millercaf24fc2013-09-10 18:37:01 -070068 private static final boolean DEBUG_WARP = false;
Jim Millerdcb3d842012-08-23 19:18:12 -070069 protected static final int INVALID_PAGE = -1;
Jim Millerc162dd02013-09-25 18:57:43 -070070 private static final int WARP_PEEK_ANIMATION_DURATION = 150;
71 private static final float WARP_ANIMATE_AMOUNT = -75.0f; // in dip
Jim Millerdcb3d842012-08-23 19:18:12 -070072
73 // the min drag distance for a fling to register, to prevent random page shifts
74 private static final int MIN_LENGTH_FOR_FLING = 25;
75
Jim Millerd6523da2012-10-21 16:47:02 -070076 protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
Jim Millerdcb3d842012-08-23 19:18:12 -070077 protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
78 protected static final float NANOTIME_DIV = 1000000000.0f;
79
80 private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
81 private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
82
83 private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
84 // The page is moved more than halfway, automatically move to the next page on touch up.
Jim Milleraa898472013-11-12 18:14:26 -080085 private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.5f;
Jim Millerdcb3d842012-08-23 19:18:12 -070086
87 // The following constants need to be scaled based on density. The scaled versions will be
88 // assigned to the corresponding member variables below.
Jim Milleraa898472013-11-12 18:14:26 -080089 private static final int FLING_THRESHOLD_VELOCITY = 1500;
Jim Millerdcb3d842012-08-23 19:18:12 -070090 private static final int MIN_SNAP_VELOCITY = 1500;
Jim Milleraa898472013-11-12 18:14:26 -080091 private static final int MIN_FLING_VELOCITY = 500;
Jim Millerdcb3d842012-08-23 19:18:12 -070092
Jim Millerd794e642013-05-22 15:53:24 -070093 // We are disabling touch interaction of the widget region for factory ROM.
Jim Miller838906b2012-10-19 18:41:25 -070094 private static final boolean DISABLE_TOUCH_INTERACTION = false;
Jim Millerd6523da2012-10-21 16:47:02 -070095 private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
96 private static final boolean DISABLE_FLING_TO_DELETE = false;
Adam Cohen258d9fc2012-10-13 20:24:26 -070097
Jim Millerdcb3d842012-08-23 19:18:12 -070098 static final int AUTOMATIC_PAGE_SPACING = -1;
99
100 protected int mFlingThresholdVelocity;
101 protected int mMinFlingVelocity;
102 protected int mMinSnapVelocity;
103
104 protected float mDensity;
105 protected float mSmoothingTime;
106 protected float mTouchX;
107
108 protected boolean mFirstLayout = true;
109
110 protected int mCurrentPage;
Jim Miller0ff7f012012-10-11 20:40:01 -0700111 protected int mChildCountOnLastMeasure;
112
Jim Millerdcb3d842012-08-23 19:18:12 -0700113 protected int mNextPage = INVALID_PAGE;
114 protected int mMaxScrollX;
115 protected Scroller mScroller;
116 private VelocityTracker mVelocityTracker;
117
Jim Millerd6523da2012-10-21 16:47:02 -0700118 private float mParentDownMotionX;
119 private float mParentDownMotionY;
Jim Millerdcb3d842012-08-23 19:18:12 -0700120 private float mDownMotionX;
Jim Millerd6523da2012-10-21 16:47:02 -0700121 private float mDownMotionY;
122 private float mDownScrollX;
Jim Millerdcb3d842012-08-23 19:18:12 -0700123 protected float mLastMotionX;
124 protected float mLastMotionXRemainder;
125 protected float mLastMotionY;
126 protected float mTotalMotionX;
127 private int mLastScreenCenter = -1;
128 private int[] mChildOffsets;
129 private int[] mChildRelativeOffsets;
130 private int[] mChildOffsetsWithLayoutScale;
Jim Miller1962e262013-09-25 17:08:48 -0700131 private String mDeleteString; // Accessibility announcement when widget is deleted
Jim Millerdcb3d842012-08-23 19:18:12 -0700132
133 protected final static int TOUCH_STATE_REST = 0;
134 protected final static int TOUCH_STATE_SCROLLING = 1;
135 protected final static int TOUCH_STATE_PREV_PAGE = 2;
136 protected final static int TOUCH_STATE_NEXT_PAGE = 3;
Jim Millerd6523da2012-10-21 16:47:02 -0700137 protected final static int TOUCH_STATE_REORDERING = 4;
Jim Millerafef5b22013-10-13 16:49:43 -0700138 protected final static int TOUCH_STATE_READY = 5; // when finger is down
Jim Millerd6523da2012-10-21 16:47:02 -0700139
Jim Millerdcb3d842012-08-23 19:18:12 -0700140 protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
Jim Millercaf24fc2013-09-10 18:37:01 -0700141 protected final static float TOUCH_SLOP_SCALE = 1.0f;
Jim Millerdcb3d842012-08-23 19:18:12 -0700142
143 protected int mTouchState = TOUCH_STATE_REST;
144 protected boolean mForceScreenScrolled = false;
145
146 protected OnLongClickListener mLongClickListener;
147
Jim Millerdcb3d842012-08-23 19:18:12 -0700148 protected int mTouchSlop;
149 private int mPagingTouchSlop;
150 private int mMaximumVelocity;
151 private int mMinimumWidth;
152 protected int mPageSpacing;
Jim Millerdcb3d842012-08-23 19:18:12 -0700153 protected int mCellCountX = 0;
154 protected int mCellCountY = 0;
Jim Millerdcb3d842012-08-23 19:18:12 -0700155 protected boolean mAllowOverScroll = true;
156 protected int mUnboundedScrollX;
157 protected int[] mTempVisiblePagesRange = new int[2];
158 protected boolean mForceDrawAllChildrenNextFrame;
159
160 // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
161 // it is equal to the scaled overscroll position. We use a separate value so as to prevent
162 // the screens from continuing to translate beyond the normal bounds.
163 protected int mOverScrollX;
164
165 // parameter that adjusts the layout to be optimized for pages with that scale factor
166 protected float mLayoutScale = 1.0f;
167
168 protected static final int INVALID_POINTER = -1;
169
170 protected int mActivePointerId = INVALID_POINTER;
171
172 private PageSwitchListener mPageSwitchListener;
173
174 protected ArrayList<Boolean> mDirtyPageContent;
175
176 // If true, syncPages and syncPageItems will be called to refresh pages
177 protected boolean mContentIsRefreshable = true;
178
179 // If true, modify alpha of neighboring pages as user scrolls left/right
Jim Millerd6523da2012-10-21 16:47:02 -0700180 protected boolean mFadeInAdjacentScreens = false;
Jim Millerdcb3d842012-08-23 19:18:12 -0700181
182 // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
183 // to switch to a new page
184 protected boolean mUsePagingTouchSlop = true;
185
186 // If true, the subclass should directly update scrollX itself in its computeScroll method
187 // (SmoothPagedView does this)
188 protected boolean mDeferScrollUpdate = false;
189
190 protected boolean mIsPageMoving = false;
191
192 // All syncs and layout passes are deferred until data is ready.
193 protected boolean mIsDataReady = true;
194
195 // Scrolling indicator
196 private ValueAnimator mScrollIndicatorAnimator;
197 private View mScrollIndicator;
198 private int mScrollIndicatorPaddingLeft;
199 private int mScrollIndicatorPaddingRight;
200 private boolean mShouldShowScrollIndicator = false;
201 private boolean mShouldShowScrollIndicatorImmediately = false;
202 protected static final int sScrollIndicatorFadeInDuration = 150;
203 protected static final int sScrollIndicatorFadeOutDuration = 650;
204 protected static final int sScrollIndicatorFlashDuration = 650;
205
Winson Chungefc49252012-10-26 15:41:27 -0700206 // The viewport whether the pages are to be contained (the actual view may be larger than the
207 // viewport)
208 private Rect mViewport = new Rect();
209
Jim Millerd6523da2012-10-21 16:47:02 -0700210 // Reordering
Jim Millerb5f3b702012-10-21 19:09:23 -0700211 // We use the min scale to determine how much to expand the actually PagedView measured
212 // dimensions such that when we are zoomed out, the view is not clipped
Jim Millerd6523da2012-10-21 16:47:02 -0700213 private int REORDERING_DROP_REPOSITION_DURATION = 200;
Winson Chung9dc99232012-10-29 17:43:18 -0700214 protected int REORDERING_REORDER_REPOSITION_DURATION = 300;
Adam Cohenf9048cd2012-10-27 16:36:10 -0700215 protected int REORDERING_ZOOM_IN_OUT_DURATION = 250;
Winson Chung9dc99232012-10-29 17:43:18 -0700216 private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 300;
Jim Millerd6523da2012-10-21 16:47:02 -0700217 private float REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE = 0.1f;
Winson Chungf3b9ec82012-11-01 14:48:51 -0700218 private long REORDERING_DELETE_DROP_TARGET_FADE_DURATION = 150;
Jim Millerd6523da2012-10-21 16:47:02 -0700219 private float mMinScale = 1f;
220 protected View mDragView;
Winson Chung48275d22012-11-05 10:56:31 -0800221 protected AnimatorSet mZoomInOutAnim;
Jim Millerd6523da2012-10-21 16:47:02 -0700222 private Runnable mSidePageHoverRunnable;
223 private int mSidePageHoverIndex = -1;
Jim Miller19a52672012-10-23 19:52:04 -0700224 // This variable's scope is only for the duration of startReordering() and endReordering()
225 private boolean mReorderingStarted = false;
226 // This variable's scope is for the duration of startReordering() and after the zoomIn()
227 // animation after endReordering()
228 private boolean mIsReordering;
Winson Chung9dc99232012-10-29 17:43:18 -0700229 // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
230 private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
231 private int mPostReorderingPreZoomInRemainingAnimationCount;
232 private Runnable mPostReorderingPreZoomInRunnable;
Jim Millerd6523da2012-10-21 16:47:02 -0700233
234 // Edge swiping
235 private boolean mOnlyAllowEdgeSwipes = false;
236 private boolean mDownEventOnEdge = false;
237 private int mEdgeSwipeRegionSize = 0;
238
239 // Convenience/caching
240 private Matrix mTmpInvMatrix = new Matrix();
241 private float[] mTmpPoint = new float[2];
Winson Chungf3b9ec82012-11-01 14:48:51 -0700242 private Rect mTmpRect = new Rect();
Winson Chungc065a5d2012-11-07 17:17:33 -0800243 private Rect mAltTmpRect = new Rect();
Jim Millerd6523da2012-10-21 16:47:02 -0700244
245 // Fling to delete
246 private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
247 private float FLING_TO_DELETE_FRICTION = 0.035f;
248 // The degrees specifies how much deviation from the up vector to still consider a fling "up"
Winson Chungf3b9ec82012-11-01 14:48:51 -0700249 private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
Winson Chungefc49252012-10-26 15:41:27 -0700250 protected int mFlingToDeleteThresholdVelocity = -1400;
Winson Chungf3b9ec82012-11-01 14:48:51 -0700251 // Drag to delete
252 private boolean mDeferringForDelete = false;
253 private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
254 private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
255
256 // Drop to delete
257 private View mDeleteDropTarget;
Jim Millerd6523da2012-10-21 16:47:02 -0700258
Winson Chung48275d22012-11-05 10:56:31 -0800259 // Bouncer
260 private boolean mTopAlignPageWhenShrinkingForBouncer = false;
261
Jim Millercaf24fc2013-09-10 18:37:01 -0700262 // Page warping
Jim Millerf4db8f92013-09-20 14:21:50 -0700263 private int mPageSwapIndex = -1; // the page we swapped out if needed
264 private int mPageWarpIndex = -1; // the page we intend to warp
Jim Millerafef5b22013-10-13 16:49:43 -0700265 private boolean mWarpPageExposed;
266 private ViewPropertyAnimator mWarpAnimation;
Jim Millerf4db8f92013-09-20 14:21:50 -0700267
Jim Millercaf24fc2013-09-10 18:37:01 -0700268 private boolean mIsCameraEvent;
Jim Millerf4db8f92013-09-20 14:21:50 -0700269 private float mWarpPeekAmount;
John Spurlock56d007b2013-10-28 18:40:56 -0400270 private boolean mOnPageEndWarpCalled;
271 private boolean mOnPageBeginWarpCalled;
Jim Millercaf24fc2013-09-10 18:37:01 -0700272
Jim Millerdcb3d842012-08-23 19:18:12 -0700273 public interface PageSwitchListener {
John Spurlockbb5c9412012-10-31 09:46:15 -0400274 void onPageSwitching(View newPage, int newPageIndex);
275 void onPageSwitched(View newPage, int newPageIndex);
Jim Millerdcb3d842012-08-23 19:18:12 -0700276 }
277
278 public PagedView(Context context) {
279 this(context, null);
280 }
281
282 public PagedView(Context context, AttributeSet attrs) {
283 this(context, attrs, 0);
284 }
285
286 public PagedView(Context context, AttributeSet attrs, int defStyle) {
287 super(context, attrs, defStyle);
288 TypedArray a = context.obtainStyledAttributes(attrs,
289 R.styleable.PagedView, defStyle, 0);
290 setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
Jim Millerdcb3d842012-08-23 19:18:12 -0700291 mScrollIndicatorPaddingLeft =
292 a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
293 mScrollIndicatorPaddingRight =
Adam Powell0b1b5522012-10-25 13:39:30 -0700294 a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
Jim Millerdcb3d842012-08-23 19:18:12 -0700295 a.recycle();
296
Winson Chung48275d22012-11-05 10:56:31 -0800297 Resources r = getResources();
298 mEdgeSwipeRegionSize = r.getDimensionPixelSize(R.dimen.kg_edge_swipe_region_size);
299 mTopAlignPageWhenShrinkingForBouncer =
300 r.getBoolean(R.bool.kg_top_align_page_shrink_on_bouncer_visible);
Adam Powell0b1b5522012-10-25 13:39:30 -0700301
Jim Millerdcb3d842012-08-23 19:18:12 -0700302 setHapticFeedbackEnabled(false);
303 init();
304 }
305
306 /**
307 * Initializes various states for this workspace.
308 */
309 protected void init() {
310 mDirtyPageContent = new ArrayList<Boolean>();
311 mDirtyPageContent.ensureCapacity(32);
312 mScroller = new Scroller(getContext(), new ScrollInterpolator());
313 mCurrentPage = 0;
Jim Millerdcb3d842012-08-23 19:18:12 -0700314
315 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
316 mTouchSlop = configuration.getScaledTouchSlop();
317 mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
318 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
319 mDensity = getResources().getDisplayMetrics().density;
Jim Millerf4db8f92013-09-20 14:21:50 -0700320 mWarpPeekAmount = mDensity * WARP_ANIMATE_AMOUNT;
Jim Millerdcb3d842012-08-23 19:18:12 -0700321
Winson Chungefc49252012-10-26 15:41:27 -0700322 // Scale the fling-to-delete threshold by the density
Jim Millerf4db8f92013-09-20 14:21:50 -0700323 mFlingToDeleteThresholdVelocity = (int) (mFlingToDeleteThresholdVelocity * mDensity);
Winson Chungefc49252012-10-26 15:41:27 -0700324
Jim Millerdcb3d842012-08-23 19:18:12 -0700325 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
326 mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
327 mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
328 setOnHierarchyChangeListener(this);
329 }
330
Winson Chungf3b9ec82012-11-01 14:48:51 -0700331 void setDeleteDropTarget(View v) {
332 mDeleteDropTarget = v;
333 }
334
Jim Millerd6523da2012-10-21 16:47:02 -0700335 // Convenience methods to map points from self to parent and vice versa
Winson Chungf3b9ec82012-11-01 14:48:51 -0700336 float[] mapPointFromViewToParent(View v, float x, float y) {
Jim Millerd6523da2012-10-21 16:47:02 -0700337 mTmpPoint[0] = x;
338 mTmpPoint[1] = y;
Winson Chungf3b9ec82012-11-01 14:48:51 -0700339 v.getMatrix().mapPoints(mTmpPoint);
340 mTmpPoint[0] += v.getLeft();
341 mTmpPoint[1] += v.getTop();
Jim Millerd6523da2012-10-21 16:47:02 -0700342 return mTmpPoint;
343 }
Winson Chungf3b9ec82012-11-01 14:48:51 -0700344 float[] mapPointFromParentToView(View v, float x, float y) {
345 mTmpPoint[0] = x - v.getLeft();
346 mTmpPoint[1] = y - v.getTop();
347 v.getMatrix().invert(mTmpInvMatrix);
Jim Millerd6523da2012-10-21 16:47:02 -0700348 mTmpInvMatrix.mapPoints(mTmpPoint);
349 return mTmpPoint;
350 }
351
352 void updateDragViewTranslationDuringDrag() {
353 float x = mLastMotionX - mDownMotionX + getScrollX() - mDownScrollX;
354 float y = mLastMotionY - mDownMotionY;
355 mDragView.setTranslationX(x);
356 mDragView.setTranslationY(y);
357
358 if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): " + x + ", " + y);
359 }
360
361 public void setMinScale(float f) {
362 mMinScale = f;
363 requestLayout();
364 }
365
366 @Override
367 public void setScaleX(float scaleX) {
368 super.setScaleX(scaleX);
Jim Miller19a52672012-10-23 19:52:04 -0700369 if (isReordering(true)) {
Winson Chungf3b9ec82012-11-01 14:48:51 -0700370 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
Jim Millerd6523da2012-10-21 16:47:02 -0700371 mLastMotionX = p[0];
372 mLastMotionY = p[1];
373 updateDragViewTranslationDuringDrag();
374 }
375 }
376
Jim Millerb5f3b702012-10-21 19:09:23 -0700377 // Convenience methods to get the actual width/height of the PagedView (since it is measured
Jim Millerd6523da2012-10-21 16:47:02 -0700378 // to be larger to account for the minimum possible scale)
Winson Chungefc49252012-10-26 15:41:27 -0700379 int getViewportWidth() {
380 return mViewport.width();
Jim Millerd6523da2012-10-21 16:47:02 -0700381 }
Winson Chungefc49252012-10-26 15:41:27 -0700382 int getViewportHeight() {
383 return mViewport.height();
Jim Millerd6523da2012-10-21 16:47:02 -0700384 }
385
Jim Millerb5f3b702012-10-21 19:09:23 -0700386 // Convenience methods to get the offset ASSUMING that we are centering the pages in the
Jim Millerd6523da2012-10-21 16:47:02 -0700387 // PagedView both horizontally and vertically
Winson Chungefc49252012-10-26 15:41:27 -0700388 int getViewportOffsetX() {
389 return (getMeasuredWidth() - getViewportWidth()) / 2;
Jim Millerd6523da2012-10-21 16:47:02 -0700390 }
Winson Chungefc49252012-10-26 15:41:27 -0700391 int getViewportOffsetY() {
392 return (getMeasuredHeight() - getViewportHeight()) / 2;
Jim Millerd6523da2012-10-21 16:47:02 -0700393 }
394
Jim Millerdcb3d842012-08-23 19:18:12 -0700395 public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
396 mPageSwitchListener = pageSwitchListener;
397 if (mPageSwitchListener != null) {
John Spurlockbb5c9412012-10-31 09:46:15 -0400398 mPageSwitchListener.onPageSwitched(getPageAt(mCurrentPage), mCurrentPage);
Jim Millerdcb3d842012-08-23 19:18:12 -0700399 }
400 }
401
402 /**
403 * Called by subclasses to mark that data is ready, and that we can begin loading and laying
404 * out pages.
405 */
406 protected void setDataIsReady() {
407 mIsDataReady = true;
408 }
409
410 protected boolean isDataReady() {
411 return mIsDataReady;
412 }
413
414 /**
415 * Returns the index of the currently displayed page.
416 *
417 * @return The index of the currently displayed page.
418 */
419 int getCurrentPage() {
420 return mCurrentPage;
421 }
422
423 int getNextPage() {
424 return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
425 }
426
427 int getPageCount() {
428 return getChildCount();
429 }
430
431 View getPageAt(int index) {
432 return getChildAt(index);
433 }
434
435 protected int indexToPage(int index) {
436 return index;
437 }
438
439 /**
440 * Updates the scroll of the current page immediately to its final scroll position. We use this
441 * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
442 * the previous tab page.
443 */
444 protected void updateCurrentPageScroll() {
445 int offset = getChildOffset(mCurrentPage);
446 int relOffset = getRelativeChildOffset(mCurrentPage);
447 int newX = offset - relOffset;
448 scrollTo(newX, 0);
449 mScroller.setFinalX(newX);
450 mScroller.forceFinished(true);
451 }
452
453 /**
454 * Sets the current page.
455 */
456 void setCurrentPage(int currentPage) {
John Spurlockbb5c9412012-10-31 09:46:15 -0400457 notifyPageSwitching(currentPage);
Jim Millerdcb3d842012-08-23 19:18:12 -0700458 if (!mScroller.isFinished()) {
459 mScroller.abortAnimation();
460 }
461 // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
462 // the default
463 if (getChildCount() == 0) {
464 return;
465 }
466
Adam Cohen7e394102012-10-13 19:10:56 -0700467 mForceScreenScrolled = true;
Jim Millerdcb3d842012-08-23 19:18:12 -0700468 mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
469 updateCurrentPageScroll();
470 updateScrollingIndicator();
John Spurlockbb5c9412012-10-31 09:46:15 -0400471 notifyPageSwitched();
Jim Millerdcb3d842012-08-23 19:18:12 -0700472 invalidate();
473 }
474
Jim Millerd6523da2012-10-21 16:47:02 -0700475 public void setOnlyAllowEdgeSwipes(boolean enable) {
476 mOnlyAllowEdgeSwipes = enable;
477 }
478
John Spurlockbb5c9412012-10-31 09:46:15 -0400479 protected void notifyPageSwitching(int whichPage) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700480 if (mPageSwitchListener != null) {
John Spurlockbb5c9412012-10-31 09:46:15 -0400481 mPageSwitchListener.onPageSwitching(getPageAt(whichPage), whichPage);
482 }
483 }
484
485 protected void notifyPageSwitched() {
486 if (mPageSwitchListener != null) {
487 mPageSwitchListener.onPageSwitched(getPageAt(mCurrentPage), mCurrentPage);
Jim Millerdcb3d842012-08-23 19:18:12 -0700488 }
489 }
490
491 protected void pageBeginMoving() {
Jim Millercaf24fc2013-09-10 18:37:01 -0700492 if (DEBUG_WARP) Log.v(TAG, "pageBeginMoving(" + mIsPageMoving + ")");
Jim Millerdcb3d842012-08-23 19:18:12 -0700493 if (!mIsPageMoving) {
494 mIsPageMoving = true;
Jim Millerc162dd02013-09-25 18:57:43 -0700495 if (isWarping()) {
John Spurlock56d007b2013-10-28 18:40:56 -0400496 dispatchOnPageBeginWarp();
Jim Millerf4db8f92013-09-20 14:21:50 -0700497 if (mPageSwapIndex != -1) {
498 swapPages(mPageSwapIndex, mPageWarpIndex);
499 }
Jim Millercaf24fc2013-09-10 18:37:01 -0700500 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700501 onPageBeginMoving();
502 }
503 }
504
John Spurlock56d007b2013-10-28 18:40:56 -0400505 private void dispatchOnPageBeginWarp() {
506 if (!mOnPageBeginWarpCalled) {
507 onPageBeginWarp();
508 mOnPageBeginWarpCalled = true;
509 }
510 mOnPageEndWarpCalled = false;
511 }
512
513 private void dispatchOnPageEndWarp() {
514 if (!mOnPageEndWarpCalled) {
515 onPageEndWarp();
516 mOnPageEndWarpCalled = true;
517 }
518 mOnPageBeginWarpCalled = false;
519 }
520
Jim Millerdcb3d842012-08-23 19:18:12 -0700521 protected void pageEndMoving() {
Jim Millercaf24fc2013-09-10 18:37:01 -0700522 if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")");
Jim Millerdcb3d842012-08-23 19:18:12 -0700523 if (mIsPageMoving) {
524 mIsPageMoving = false;
Jim Millerc162dd02013-09-25 18:57:43 -0700525 if (isWarping()) {
Jim Millerf4db8f92013-09-20 14:21:50 -0700526 if (mPageSwapIndex != -1) {
527 swapPages(mPageSwapIndex, mPageWarpIndex);
Jim Millerf4db8f92013-09-20 14:21:50 -0700528 }
John Spurlock56d007b2013-10-28 18:40:56 -0400529 dispatchOnPageEndWarp();
Jim Millerc162dd02013-09-25 18:57:43 -0700530 resetPageWarp();
Jim Millercaf24fc2013-09-10 18:37:01 -0700531 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700532 onPageEndMoving();
533 }
534 }
535
Jim Millerf4db8f92013-09-20 14:21:50 -0700536 private void resetPageWarp() {
537 // TODO: Verify pages have been reset correctly
538 mPageSwapIndex = -1;
539 mPageWarpIndex = -1;
540 }
541
Jim Millerdcb3d842012-08-23 19:18:12 -0700542 protected boolean isPageMoving() {
543 return mIsPageMoving;
544 }
545
546 // a method that subclasses can override to add behavior
547 protected void onPageBeginMoving() {
548 }
549
550 // a method that subclasses can override to add behavior
551 protected void onPageEndMoving() {
552 }
553
554 /**
555 * Registers the specified listener on each page contained in this workspace.
556 *
557 * @param l The listener used to respond to long clicks.
558 */
559 @Override
560 public void setOnLongClickListener(OnLongClickListener l) {
561 mLongClickListener = l;
562 final int count = getPageCount();
563 for (int i = 0; i < count; i++) {
564 getPageAt(i).setOnLongClickListener(l);
565 }
566 }
567
568 @Override
569 public void scrollBy(int x, int y) {
570 scrollTo(mUnboundedScrollX + x, getScrollY() + y);
571 }
572
573 @Override
574 public void scrollTo(int x, int y) {
575 mUnboundedScrollX = x;
576
577 if (x < 0) {
578 super.scrollTo(0, y);
579 if (mAllowOverScroll) {
580 overScroll(x);
581 }
582 } else if (x > mMaxScrollX) {
583 super.scrollTo(mMaxScrollX, y);
584 if (mAllowOverScroll) {
585 overScroll(x - mMaxScrollX);
586 }
587 } else {
588 mOverScrollX = x;
589 super.scrollTo(x, y);
590 }
591
592 mTouchX = x;
593 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
Jim Millerd6523da2012-10-21 16:47:02 -0700594
595 // Update the last motion events when scrolling
Jim Miller19a52672012-10-23 19:52:04 -0700596 if (isReordering(true)) {
Winson Chungf3b9ec82012-11-01 14:48:51 -0700597 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
Jim Millerd6523da2012-10-21 16:47:02 -0700598 mLastMotionX = p[0];
599 mLastMotionY = p[1];
600 updateDragViewTranslationDuringDrag();
601 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700602 }
603
604 // we moved this functionality to a helper function so SmoothPagedView can reuse it
605 protected boolean computeScrollHelper() {
606 if (mScroller.computeScrollOffset()) {
607 // Don't bother scrolling if the page does not need to be moved
608 if (getScrollX() != mScroller.getCurrX()
609 || getScrollY() != mScroller.getCurrY()
610 || mOverScrollX != mScroller.getCurrX()) {
611 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
612 }
613 invalidate();
614 return true;
615 } else if (mNextPage != INVALID_PAGE) {
616 mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
617 mNextPage = INVALID_PAGE;
John Spurlockbb5c9412012-10-31 09:46:15 -0400618 notifyPageSwitched();
Jim Millerdcb3d842012-08-23 19:18:12 -0700619
620 // We don't want to trigger a page end moving unless the page has settled
621 // and the user has stopped scrolling
622 if (mTouchState == TOUCH_STATE_REST) {
623 pageEndMoving();
624 }
Winson Chungf3b9ec82012-11-01 14:48:51 -0700625
Winson Chung9dc99232012-10-29 17:43:18 -0700626 onPostReorderingAnimationCompleted();
Jim Millerdcb3d842012-08-23 19:18:12 -0700627 return true;
628 }
629 return false;
630 }
631
Jim Millerdcb3d842012-08-23 19:18:12 -0700632 @Override
633 public void computeScroll() {
634 computeScrollHelper();
635 }
636
Winson Chung6cf53bb2012-11-05 17:55:42 -0800637 protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
638 return mTopAlignPageWhenShrinkingForBouncer;
639 }
640
Jim Millerdcb3d842012-08-23 19:18:12 -0700641 @Override
642 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
643 if (!mIsDataReady || getChildCount() == 0) {
644 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
645 return;
646 }
647
Jim Millerd6523da2012-10-21 16:47:02 -0700648 // We measure the dimensions of the PagedView to be larger than the pages so that when we
649 // zoom out (and scale down), the view is still contained in the parent
Winson Chungefc49252012-10-26 15:41:27 -0700650 View parent = (View) getParent();
651 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
652 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
653 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
654 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
655 // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the
656 // viewport, we can be at most one and a half screens offset once we scale down
Winson Chung6cf53bb2012-11-05 17:55:42 -0800657 DisplayMetrics dm = getResources().getDisplayMetrics();
658 int maxSize = Math.max(dm.widthPixels, dm.heightPixels);
659 int parentWidthSize = (int) (1.5f * maxSize);
660 int parentHeightSize = maxSize;
Winson Chungefc49252012-10-26 15:41:27 -0700661 int scaledWidthSize = (int) (parentWidthSize / mMinScale);
662 int scaledHeightSize = (int) (parentHeightSize / mMinScale);
663 mViewport.set(0, 0, widthSize, heightSize);
Jim Millerdcb3d842012-08-23 19:18:12 -0700664
665 if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
666 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
667 return;
668 }
669
670 // Return early if we aren't given a proper dimension
671 if (widthSize <= 0 || heightSize <= 0) {
672 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
673 return;
674 }
675
676 /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
677 * of the All apps view on XLarge displays to not take up more space then it needs. Width
678 * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
679 * each page to have the same width.
680 */
681 final int verticalPadding = getPaddingTop() + getPaddingBottom();
682 final int horizontalPadding = getPaddingLeft() + getPaddingRight();
683
684 // The children are given the same width and height as the workspace
685 // unless they were set to WRAP_CONTENT
686 if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
Winson Chungefc49252012-10-26 15:41:27 -0700687 if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
688 if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
Jim Millerd6523da2012-10-21 16:47:02 -0700689 if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
Winson Chungefc49252012-10-26 15:41:27 -0700690 if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
Jim Millerdcb3d842012-08-23 19:18:12 -0700691 final int childCount = getChildCount();
692 for (int i = 0; i < childCount; i++) {
693 // disallowing padding in paged view (just pass 0)
694 final View child = getPageAt(i);
Jim Millerd6523da2012-10-21 16:47:02 -0700695 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Jim Millerdcb3d842012-08-23 19:18:12 -0700696
Jim Millerd6523da2012-10-21 16:47:02 -0700697 int childWidthMode;
698 if (lp.width == LayoutParams.WRAP_CONTENT) {
699 childWidthMode = MeasureSpec.AT_MOST;
700 } else {
701 childWidthMode = MeasureSpec.EXACTLY;
702 }
703
704 int childHeightMode;
705 if (lp.height == LayoutParams.WRAP_CONTENT) {
706 childHeightMode = MeasureSpec.AT_MOST;
707 } else {
708 childHeightMode = MeasureSpec.EXACTLY;
709 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700710
711 final int childWidthMeasureSpec =
712 MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
713 final int childHeightMeasureSpec =
714 MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
715
716 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
717 }
Jim Millerd6523da2012-10-21 16:47:02 -0700718 setMeasuredDimension(scaledWidthSize, scaledHeightSize);
Jim Millerdcb3d842012-08-23 19:18:12 -0700719
720 // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
721 // We also wait until we set the measured dimensions before flushing the cache as well, to
722 // ensure that the cache is filled with good values.
723 invalidateCachedOffsets();
724
Winson Chungf3b9ec82012-11-01 14:48:51 -0700725 if (mChildCountOnLastMeasure != getChildCount() && !mDeferringForDelete) {
Jim Miller0ff7f012012-10-11 20:40:01 -0700726 setCurrentPage(mCurrentPage);
727 }
728 mChildCountOnLastMeasure = getChildCount();
729
Jim Millerdcb3d842012-08-23 19:18:12 -0700730 if (childCount > 0) {
Winson Chungefc49252012-10-26 15:41:27 -0700731 if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getViewportWidth() + ", "
Jim Millerdcb3d842012-08-23 19:18:12 -0700732 + getChildWidth(0));
733
734 // Calculate the variable page spacing if necessary
735 if (mPageSpacing == AUTOMATIC_PAGE_SPACING) {
736 // The gap between pages in the PagedView should be equal to the gap from the page
737 // to the edge of the screen (so it is not visible in the current screen). To
738 // account for unequal padding on each side of the paged view, we take the maximum
739 // of the left/right gap and use that as the gap between each page.
740 int offset = getRelativeChildOffset(0);
741 int spacing = Math.max(offset, widthSize - offset -
742 getChildAt(0).getMeasuredWidth());
743 setPageSpacing(spacing);
744 }
745 }
746
747 updateScrollingIndicatorPosition();
748
749 if (childCount > 0) {
750 mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
751 } else {
752 mMaxScrollX = 0;
753 }
754 }
755
756 public void setPageSpacing(int pageSpacing) {
757 mPageSpacing = pageSpacing;
758 invalidateCachedOffsets();
759 }
760
761 @Override
762 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
763 if (!mIsDataReady || getChildCount() == 0) {
764 return;
765 }
766
767 if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
Jim Millerdcb3d842012-08-23 19:18:12 -0700768 final int childCount = getChildCount();
Jim Millerdcb3d842012-08-23 19:18:12 -0700769
Winson Chungefc49252012-10-26 15:41:27 -0700770 int offsetX = getViewportOffsetX();
771 int offsetY = getViewportOffsetY();
Jim Millerd6523da2012-10-21 16:47:02 -0700772
Winson Chungefc49252012-10-26 15:41:27 -0700773 // Update the viewport offsets
774 mViewport.offset(offsetX, offsetY);
775
776 int childLeft = offsetX + getRelativeChildOffset(0);
Jim Millerdcb3d842012-08-23 19:18:12 -0700777 for (int i = 0; i < childCount; i++) {
778 final View child = getPageAt(i);
Winson Chungefc49252012-10-26 15:41:27 -0700779 int childTop = offsetY + getPaddingTop();
Jim Millerdcb3d842012-08-23 19:18:12 -0700780 if (child.getVisibility() != View.GONE) {
781 final int childWidth = getScaledMeasuredWidth(child);
782 final int childHeight = child.getMeasuredHeight();
Jim Millerdcb3d842012-08-23 19:18:12 -0700783
784 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
785 child.layout(childLeft, childTop,
786 childLeft + child.getMeasuredWidth(), childTop + childHeight);
787 childLeft += childWidth + mPageSpacing;
788 }
789 }
790
791 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
792 setHorizontalScrollBarEnabled(false);
793 updateCurrentPageScroll();
794 setHorizontalScrollBarEnabled(true);
795 mFirstLayout = false;
796 }
Jim Millercaf24fc2013-09-10 18:37:01 -0700797 // If a page was swapped when we rebuilt the layout, swap it again now.
798 if (mPageSwapIndex != -1) {
799 if (DEBUG_WARP) Log.v(TAG, "onLayout: swapping pages");
Jim Millerf4db8f92013-09-20 14:21:50 -0700800 swapPages(mPageSwapIndex, mPageWarpIndex);
Jim Millercaf24fc2013-09-10 18:37:01 -0700801 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700802 }
803
804 protected void screenScrolled(int screenCenter) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700805 }
806
807 @Override
808 public void onChildViewAdded(View parent, View child) {
809 // This ensures that when children are added, they get the correct transforms / alphas
810 // in accordance with any scroll effects.
811 mForceScreenScrolled = true;
812 invalidate();
813 invalidateCachedOffsets();
814 }
815
816 @Override
817 public void onChildViewRemoved(View parent, View child) {
Jim Millerd6523da2012-10-21 16:47:02 -0700818 mForceScreenScrolled = true;
Jim Millere5fb5e42013-04-10 16:10:06 -0700819 invalidate();
820 invalidateCachedOffsets();
Jim Millerdcb3d842012-08-23 19:18:12 -0700821 }
822
823 protected void invalidateCachedOffsets() {
824 int count = getChildCount();
825 if (count == 0) {
826 mChildOffsets = null;
827 mChildRelativeOffsets = null;
828 mChildOffsetsWithLayoutScale = null;
829 return;
830 }
831
832 mChildOffsets = new int[count];
833 mChildRelativeOffsets = new int[count];
834 mChildOffsetsWithLayoutScale = new int[count];
835 for (int i = 0; i < count; i++) {
836 mChildOffsets[i] = -1;
837 mChildRelativeOffsets[i] = -1;
838 mChildOffsetsWithLayoutScale[i] = -1;
839 }
840 }
841
842 protected int getChildOffset(int index) {
Jim Miller0ff7f012012-10-11 20:40:01 -0700843 if (index < 0 || index > getChildCount() - 1) return 0;
844
Jim Millerdcb3d842012-08-23 19:18:12 -0700845 int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
846 mChildOffsets : mChildOffsetsWithLayoutScale;
847
848 if (childOffsets != null && childOffsets[index] != -1) {
849 return childOffsets[index];
850 } else {
851 if (getChildCount() == 0)
852 return 0;
853
854 int offset = getRelativeChildOffset(0);
855 for (int i = 0; i < index; ++i) {
856 offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
857 }
858 if (childOffsets != null) {
859 childOffsets[index] = offset;
860 }
861 return offset;
862 }
863 }
864
865 protected int getRelativeChildOffset(int index) {
Jim Miller0ff7f012012-10-11 20:40:01 -0700866 if (index < 0 || index > getChildCount() - 1) return 0;
867
Jim Millerdcb3d842012-08-23 19:18:12 -0700868 if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
869 return mChildRelativeOffsets[index];
870 } else {
871 final int padding = getPaddingLeft() + getPaddingRight();
872 final int offset = getPaddingLeft() +
Winson Chungefc49252012-10-26 15:41:27 -0700873 (getViewportWidth() - padding - getChildWidth(index)) / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -0700874 if (mChildRelativeOffsets != null) {
875 mChildRelativeOffsets[index] = offset;
876 }
877 return offset;
878 }
879 }
880
881 protected int getScaledMeasuredWidth(View child) {
882 // This functions are called enough times that it actually makes a difference in the
883 // profiler -- so just inline the max() here
884 final int measuredWidth = child.getMeasuredWidth();
885 final int minWidth = mMinimumWidth;
886 final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
887 return (int) (maxWidth * mLayoutScale + 0.5f);
888 }
889
Jim Miller19a52672012-10-23 19:52:04 -0700890 void boundByReorderablePages(boolean isReordering, int[] range) {
Winson Chungefc49252012-10-26 15:41:27 -0700891 // Do nothing
Jim Miller19a52672012-10-23 19:52:04 -0700892 }
893
Jim Millerd6523da2012-10-21 16:47:02 -0700894 // TODO: Fix this
Jim Millerdcb3d842012-08-23 19:18:12 -0700895 protected void getVisiblePages(int[] range) {
Jim Millerd6523da2012-10-21 16:47:02 -0700896 range[0] = 0;
897 range[1] = getPageCount() - 1;
Jim Miller19a52672012-10-23 19:52:04 -0700898
Jim Millerd6523da2012-10-21 16:47:02 -0700899 /*
Jim Millerdcb3d842012-08-23 19:18:12 -0700900 final int pageCount = getChildCount();
901
902 if (pageCount > 0) {
Winson Chungefc49252012-10-26 15:41:27 -0700903 final int screenWidth = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -0700904 int leftScreen = 0;
905 int rightScreen = 0;
Winson Chungefc49252012-10-26 15:41:27 -0700906 int offsetX = getViewportOffsetX() + getScrollX();
Jim Millerdcb3d842012-08-23 19:18:12 -0700907 View currPage = getPageAt(leftScreen);
908 while (leftScreen < pageCount - 1 &&
909 currPage.getX() + currPage.getWidth() -
Jim Millerd6523da2012-10-21 16:47:02 -0700910 currPage.getPaddingRight() < offsetX) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700911 leftScreen++;
912 currPage = getPageAt(leftScreen);
913 }
914 rightScreen = leftScreen;
915 currPage = getPageAt(rightScreen + 1);
916 while (rightScreen < pageCount - 1 &&
Jim Millerd6523da2012-10-21 16:47:02 -0700917 currPage.getX() - currPage.getPaddingLeft() < offsetX + screenWidth) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700918 rightScreen++;
919 currPage = getPageAt(rightScreen + 1);
920 }
Jim Millerd6523da2012-10-21 16:47:02 -0700921
922 // TEMP: this is a hacky way to ensure that animations to new pages are not clipped
923 // because we don't draw them while scrolling?
924 range[0] = Math.max(0, leftScreen - 1);
925 range[1] = Math.min(rightScreen + 1, getChildCount() - 1);
Jim Millerdcb3d842012-08-23 19:18:12 -0700926 } else {
927 range[0] = -1;
928 range[1] = -1;
929 }
Jim Millerd6523da2012-10-21 16:47:02 -0700930 */
Jim Millerdcb3d842012-08-23 19:18:12 -0700931 }
932
933 protected boolean shouldDrawChild(View child) {
934 return child.getAlpha() > 0;
935 }
936
937 @Override
938 protected void dispatchDraw(Canvas canvas) {
Winson Chungefc49252012-10-26 15:41:27 -0700939 int halfScreenSize = getViewportWidth() / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -0700940 // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
941 // Otherwise it is equal to the scaled overscroll position.
942 int screenCenter = mOverScrollX + halfScreenSize;
943
944 if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
945 // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
946 // set it for the next frame
947 mForceScreenScrolled = false;
948 screenScrolled(screenCenter);
949 mLastScreenCenter = screenCenter;
950 }
951
952 // Find out which screens are visible; as an optimization we only call draw on them
953 final int pageCount = getChildCount();
954 if (pageCount > 0) {
955 getVisiblePages(mTempVisiblePagesRange);
956 final int leftScreen = mTempVisiblePagesRange[0];
957 final int rightScreen = mTempVisiblePagesRange[1];
958 if (leftScreen != -1 && rightScreen != -1) {
959 final long drawingTime = getDrawingTime();
960 // Clip to the bounds
961 canvas.save();
962 canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
963 getScrollY() + getBottom() - getTop());
964
Jim Millerd6523da2012-10-21 16:47:02 -0700965 // Draw all the children, leaving the drag view for last
966 for (int i = pageCount - 1; i >= 0; i--) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700967 final View v = getPageAt(i);
Jim Millerd6523da2012-10-21 16:47:02 -0700968 if (v == mDragView) continue;
Jim Millerdcb3d842012-08-23 19:18:12 -0700969 if (mForceDrawAllChildrenNextFrame ||
970 (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
971 drawChild(canvas, v, drawingTime);
972 }
973 }
Jim Millerd6523da2012-10-21 16:47:02 -0700974 // Draw the drag view on top (if there is one)
975 if (mDragView != null) {
976 drawChild(canvas, mDragView, drawingTime);
977 }
978
Jim Millerdcb3d842012-08-23 19:18:12 -0700979 mForceDrawAllChildrenNextFrame = false;
980 canvas.restore();
981 }
982 }
983 }
984
985 @Override
986 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
987 int page = indexToPage(indexOfChild(child));
988 if (page != mCurrentPage || !mScroller.isFinished()) {
989 snapToPage(page);
990 return true;
991 }
992 return false;
993 }
994
995 @Override
996 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
997 int focusablePage;
998 if (mNextPage != INVALID_PAGE) {
999 focusablePage = mNextPage;
1000 } else {
1001 focusablePage = mCurrentPage;
1002 }
1003 View v = getPageAt(focusablePage);
1004 if (v != null) {
1005 return v.requestFocus(direction, previouslyFocusedRect);
1006 }
1007 return false;
1008 }
1009
1010 @Override
1011 public boolean dispatchUnhandledMove(View focused, int direction) {
1012 if (direction == View.FOCUS_LEFT) {
1013 if (getCurrentPage() > 0) {
1014 snapToPage(getCurrentPage() - 1);
1015 return true;
1016 }
1017 } else if (direction == View.FOCUS_RIGHT) {
1018 if (getCurrentPage() < getPageCount() - 1) {
1019 snapToPage(getCurrentPage() + 1);
1020 return true;
1021 }
1022 }
1023 return super.dispatchUnhandledMove(focused, direction);
1024 }
1025
1026 @Override
1027 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1028 if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
1029 getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
1030 }
1031 if (direction == View.FOCUS_LEFT) {
1032 if (mCurrentPage > 0) {
1033 getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
1034 }
1035 } else if (direction == View.FOCUS_RIGHT){
1036 if (mCurrentPage < getPageCount() - 1) {
1037 getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
1038 }
1039 }
1040 }
1041
1042 /**
1043 * If one of our descendant views decides that it could be focused now, only
1044 * pass that along if it's on the current page.
1045 *
1046 * This happens when live folders requery, and if they're off page, they
1047 * end up calling requestFocus, which pulls it on page.
1048 */
1049 @Override
1050 public void focusableViewAvailable(View focused) {
1051 View current = getPageAt(mCurrentPage);
1052 View v = focused;
1053 while (true) {
1054 if (v == current) {
1055 super.focusableViewAvailable(focused);
1056 return;
1057 }
1058 if (v == this) {
1059 return;
1060 }
1061 ViewParent parent = v.getParent();
1062 if (parent instanceof View) {
1063 v = (View)v.getParent();
1064 } else {
1065 return;
1066 }
1067 }
1068 }
1069
1070 /**
Jim Millerdcb3d842012-08-23 19:18:12 -07001071 * Return true if a tap at (x, y) should trigger a flip to the previous page.
1072 */
1073 protected boolean hitsPreviousPage(float x, float y) {
Winson Chungefc49252012-10-26 15:41:27 -07001074 return (x < getViewportOffsetX() + getRelativeChildOffset(mCurrentPage) - mPageSpacing);
Jim Millerdcb3d842012-08-23 19:18:12 -07001075 }
1076
1077 /**
1078 * Return true if a tap at (x, y) should trigger a flip to the next page.
1079 */
1080 protected boolean hitsNextPage(float x, float y) {
Winson Chungefc49252012-10-26 15:41:27 -07001081 return (x > (getViewportOffsetX() + getViewportWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
Jim Millerdcb3d842012-08-23 19:18:12 -07001082 }
1083
Winson Chung1272e0e2012-11-26 17:05:37 -08001084 /** Returns whether x and y originated within the buffered viewport */
1085 private boolean isTouchPointInViewportWithBuffer(int x, int y) {
1086 mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
1087 mViewport.right + mViewport.width() / 2, mViewport.bottom);
1088 return mTmpRect.contains(x, y);
1089 }
1090
1091 /** Returns whether x and y originated within the current page view bounds */
1092 private boolean isTouchPointInCurrentPage(int x, int y) {
1093 View v = getPageAt(getCurrentPage());
1094 if (v != null) {
1095 mTmpRect.set((v.getLeft() - getScrollX()), 0, (v.getRight() - getScrollX()),
1096 v.getBottom());
Winson Chung6cf53bb2012-11-05 17:55:42 -08001097 return mTmpRect.contains(x, y);
Winson Chung6cf53bb2012-11-05 17:55:42 -08001098 }
Winson Chung1272e0e2012-11-26 17:05:37 -08001099 return false;
Winson Chung6cf53bb2012-11-05 17:55:42 -08001100 }
1101
Jim Millerdcb3d842012-08-23 19:18:12 -07001102 @Override
1103 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohen258d9fc2012-10-13 20:24:26 -07001104 if (DISABLE_TOUCH_INTERACTION) {
1105 return false;
1106 }
1107
Jim Millerdcb3d842012-08-23 19:18:12 -07001108 /*
1109 * This method JUST determines whether we want to intercept the motion.
1110 * If we return true, onTouchEvent will be called and we do the actual
1111 * scrolling there.
1112 */
1113 acquireVelocityTrackerAndAddMovement(ev);
1114
1115 // Skip touch handling if there are no pages to swipe
1116 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
1117
1118 /*
1119 * Shortcut the most recurring case: the user is in the dragging
1120 * state and he is moving his finger. We want to intercept this
1121 * motion.
1122 */
1123 final int action = ev.getAction();
1124 if ((action == MotionEvent.ACTION_MOVE) &&
1125 (mTouchState == TOUCH_STATE_SCROLLING)) {
1126 return true;
1127 }
1128
1129 switch (action & MotionEvent.ACTION_MASK) {
1130 case MotionEvent.ACTION_MOVE: {
1131 /*
1132 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1133 * whether the user has moved far enough from his original down touch.
1134 */
1135 if (mActivePointerId != INVALID_POINTER) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001136 if (mIsCameraEvent || determineScrollingStart(ev)) {
1137 startScrolling(ev);
1138 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001139 break;
1140 }
1141 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
1142 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
1143 // i.e. fall through to the next case (don't break)
1144 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
1145 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
Jim Miller1962e262013-09-25 17:08:48 -07001146
1147 break;
Jim Millerdcb3d842012-08-23 19:18:12 -07001148 }
1149
1150 case MotionEvent.ACTION_DOWN: {
Jim Millerafef5b22013-10-13 16:49:43 -07001151 if (mIsCameraEvent) {
Jim Millerc162dd02013-09-25 18:57:43 -07001152 animateWarpPageOnScreen("interceptTouch(): DOWN");
Jim Millerf4db8f92013-09-20 14:21:50 -07001153 }
Jim Millercaf24fc2013-09-10 18:37:01 -07001154 // Remember where the motion event started
1155 saveDownState(ev);
Jim Millerdcb3d842012-08-23 19:18:12 -07001156
1157 /*
1158 * If being flinged and user touches the screen, initiate drag;
1159 * otherwise don't. mScroller.isFinished should be false when
1160 * being flinged.
1161 */
1162 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
1163 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
1164 if (finishedScrolling) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001165 setTouchState(TOUCH_STATE_REST);
Jim Millerdcb3d842012-08-23 19:18:12 -07001166 mScroller.abortAnimation();
1167 } else {
Jim Millercaf24fc2013-09-10 18:37:01 -07001168 if (mIsCameraEvent || isTouchPointInViewportWithBuffer(
1169 (int) mDownMotionX, (int) mDownMotionY)) {
1170 setTouchState(TOUCH_STATE_SCROLLING);
Winson Chung6cf53bb2012-11-05 17:55:42 -08001171 } else {
Jim Millercaf24fc2013-09-10 18:37:01 -07001172 setTouchState(TOUCH_STATE_REST);
Winson Chung6cf53bb2012-11-05 17:55:42 -08001173 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001174 }
1175
1176 // check if this can be the beginning of a tap on the side of the pages
1177 // to scroll the current page
Jim Millerd6523da2012-10-21 16:47:02 -07001178 if (!DISABLE_TOUCH_SIDE_PAGES) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001179 if (mTouchState != TOUCH_STATE_PREV_PAGE
1180 && mTouchState != TOUCH_STATE_NEXT_PAGE) {
Jim Millerd6523da2012-10-21 16:47:02 -07001181 if (getChildCount() > 0) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001182 float x = ev.getX();
1183 float y = ev.getY();
Jim Millerd6523da2012-10-21 16:47:02 -07001184 if (hitsPreviousPage(x, y)) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001185 setTouchState(TOUCH_STATE_PREV_PAGE);
Jim Millerd6523da2012-10-21 16:47:02 -07001186 } else if (hitsNextPage(x, y)) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001187 setTouchState(TOUCH_STATE_NEXT_PAGE);
Jim Millerd6523da2012-10-21 16:47:02 -07001188 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001189 }
1190 }
1191 }
1192 break;
1193 }
1194
1195 case MotionEvent.ACTION_UP:
1196 case MotionEvent.ACTION_CANCEL:
Jim Millerd6523da2012-10-21 16:47:02 -07001197 resetTouchState();
Winson Chung6cf53bb2012-11-05 17:55:42 -08001198 // Just intercept the touch event on up if we tap outside the strict viewport
Winson Chung1272e0e2012-11-26 17:05:37 -08001199 if (!isTouchPointInCurrentPage((int) mLastMotionX, (int) mLastMotionY)) {
Winson Chung6cf53bb2012-11-05 17:55:42 -08001200 return true;
1201 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001202 break;
1203
1204 case MotionEvent.ACTION_POINTER_UP:
1205 onSecondaryPointerUp(ev);
1206 releaseVelocityTracker();
1207 break;
1208 }
1209
1210 /*
1211 * The only time we want to intercept motion events is if we are in the
1212 * drag mode.
1213 */
1214 return mTouchState != TOUCH_STATE_REST;
1215 }
1216
Jim Millercaf24fc2013-09-10 18:37:01 -07001217 private void setTouchState(int touchState) {
1218 if (mTouchState != touchState) {
Jim Millerafef5b22013-10-13 16:49:43 -07001219 if (DEBUG_WARP) Log.v(TAG, "mTouchState changing to " + touchState);
Jim Millercaf24fc2013-09-10 18:37:01 -07001220 onTouchStateChanged(touchState);
1221 mTouchState = touchState;
1222 }
1223 }
1224
1225 void onTouchStateChanged(int newTouchState) {
1226 if (DEBUG) {
1227 Log.v(TAG, "onTouchStateChanged(old="+ mTouchState + ", new=" + newTouchState + ")");
1228 }
1229 }
1230
1231 /**
1232 * Save the state when we get {@link MotionEvent#ACTION_DOWN}
1233 * @param ev
1234 */
1235 private void saveDownState(MotionEvent ev) {
1236 // Remember where the motion event started
1237 mDownMotionX = mLastMotionX = ev.getX();
1238 mDownMotionY = mLastMotionY = ev.getY();
1239 mDownScrollX = getScrollX();
1240 float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1241 mParentDownMotionX = p[0];
1242 mParentDownMotionY = p[1];
1243 mLastMotionXRemainder = 0;
1244 mTotalMotionX = 0;
1245 mActivePointerId = ev.getPointerId(0);
1246
1247 // Determine if the down event is within the threshold to be an edge swipe
1248 int leftEdgeBoundary = getViewportOffsetX() + mEdgeSwipeRegionSize;
1249 int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize;
1250 if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) {
1251 mDownEventOnEdge = true;
1252 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001253 }
1254
1255 /*
1256 * Determines if we should change the touch state to start scrolling after the
1257 * user moves their touch point too far.
1258 */
Jim Millercaf24fc2013-09-10 18:37:01 -07001259 protected boolean determineScrollingStart(MotionEvent ev) {
Winson Chung6cf53bb2012-11-05 17:55:42 -08001260 // Disallow scrolling if we don't have a valid pointer index
Jim Millerdcb3d842012-08-23 19:18:12 -07001261 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
Jim Millercaf24fc2013-09-10 18:37:01 -07001262 if (pointerIndex == -1) return false;
Jim Millerd6523da2012-10-21 16:47:02 -07001263
Winson Chung6cf53bb2012-11-05 17:55:42 -08001264 // Disallow scrolling if we started the gesture from outside the viewport
1265 final float x = ev.getX(pointerIndex);
1266 final float y = ev.getY(pointerIndex);
Jim Millercaf24fc2013-09-10 18:37:01 -07001267 if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return false;
Jim Millerd6523da2012-10-21 16:47:02 -07001268
1269 // If we're only allowing edge swipes, we break out early if the down event wasn't
1270 // at the edge.
Jim Millercaf24fc2013-09-10 18:37:01 -07001271 if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) return false;
Jim Millerd6523da2012-10-21 16:47:02 -07001272
Jim Millerdcb3d842012-08-23 19:18:12 -07001273 final int xDiff = (int) Math.abs(x - mLastMotionX);
1274 final int yDiff = (int) Math.abs(y - mLastMotionY);
1275
Jim Millercaf24fc2013-09-10 18:37:01 -07001276 final int touchSlop = Math.round(TOUCH_SLOP_SCALE * mTouchSlop);
Jim Millerdcb3d842012-08-23 19:18:12 -07001277 boolean xPaged = xDiff > mPagingTouchSlop;
1278 boolean xMoved = xDiff > touchSlop;
1279 boolean yMoved = yDiff > touchSlop;
1280
Jim Millercaf24fc2013-09-10 18:37:01 -07001281 return (xMoved || xPaged || yMoved) && (mUsePagingTouchSlop ? xPaged : xMoved);
1282 }
1283
1284 private void startScrolling(MotionEvent ev) {
1285 // Ignore if we don't have a valid pointer index
1286 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1287 if (pointerIndex == -1) return;
1288
1289 final float x = ev.getX(pointerIndex);
1290 setTouchState(TOUCH_STATE_SCROLLING);
1291 mTotalMotionX += Math.abs(mLastMotionX - x);
1292 mLastMotionX = x;
1293 mLastMotionXRemainder = 0;
1294 mTouchX = getViewportOffsetX() + getScrollX();
1295 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1296 pageBeginMoving();
Jim Millerdcb3d842012-08-23 19:18:12 -07001297 }
1298
Adam Cohen9ec871d2012-10-24 19:25:44 -07001299 protected float getMaxScrollProgress() {
1300 return 1.0f;
1301 }
1302
Adam Cohenf9048cd2012-10-27 16:36:10 -07001303 protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
1304 final int halfScreenSize = getViewportWidth() / 2;
1305
1306 screenCenter = Math.min(mScrollX + halfScreenSize, screenCenter);
1307 screenCenter = Math.max(halfScreenSize, screenCenter);
1308
1309 return getScrollProgress(screenCenter, v, page);
1310 }
1311
Jim Millerdcb3d842012-08-23 19:18:12 -07001312 protected float getScrollProgress(int screenCenter, View v, int page) {
Winson Chungefc49252012-10-26 15:41:27 -07001313 final int halfScreenSize = getViewportWidth() / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -07001314
1315 int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
1316 int delta = screenCenter - (getChildOffset(page) -
1317 getRelativeChildOffset(page) + halfScreenSize);
1318
1319 float scrollProgress = delta / (totalDistance * 1.0f);
Adam Cohen9ec871d2012-10-24 19:25:44 -07001320 scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
1321 scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
Jim Millerdcb3d842012-08-23 19:18:12 -07001322 return scrollProgress;
1323 }
1324
1325 // This curve determines how the effect of scrolling over the limits of the page dimishes
1326 // as the user pulls further and further from the bounds
1327 private float overScrollInfluenceCurve(float f) {
1328 f -= 1.0f;
1329 return f * f * f + 1.0f;
1330 }
1331
1332 protected void acceleratedOverScroll(float amount) {
Winson Chungefc49252012-10-26 15:41:27 -07001333 int screenSize = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -07001334
1335 // We want to reach the max over scroll effect when the user has
1336 // over scrolled half the size of the screen
1337 float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1338
1339 if (f == 0) return;
1340
1341 // Clamp this factor, f, to -1 < f < 1
1342 if (Math.abs(f) >= 1) {
1343 f /= Math.abs(f);
1344 }
1345
1346 int overScrollAmount = (int) Math.round(f * screenSize);
1347 if (amount < 0) {
1348 mOverScrollX = overScrollAmount;
1349 super.scrollTo(0, getScrollY());
1350 } else {
1351 mOverScrollX = mMaxScrollX + overScrollAmount;
1352 super.scrollTo(mMaxScrollX, getScrollY());
1353 }
1354 invalidate();
1355 }
1356
1357 protected void dampedOverScroll(float amount) {
Winson Chungefc49252012-10-26 15:41:27 -07001358 int screenSize = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -07001359
1360 float f = (amount / screenSize);
1361
1362 if (f == 0) return;
1363 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1364
1365 // Clamp this factor, f, to -1 < f < 1
1366 if (Math.abs(f) >= 1) {
1367 f /= Math.abs(f);
1368 }
1369
1370 int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
1371 if (amount < 0) {
1372 mOverScrollX = overScrollAmount;
1373 super.scrollTo(0, getScrollY());
1374 } else {
1375 mOverScrollX = mMaxScrollX + overScrollAmount;
1376 super.scrollTo(mMaxScrollX, getScrollY());
1377 }
1378 invalidate();
1379 }
1380
1381 protected void overScroll(float amount) {
1382 dampedOverScroll(amount);
1383 }
1384
1385 protected float maxOverScroll() {
1386 // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1387 // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1388 float f = 1.0f;
1389 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1390 return OVERSCROLL_DAMP_FACTOR * f;
1391 }
Jim Millerb5f3b702012-10-21 19:09:23 -07001392
Jim Millerdcb3d842012-08-23 19:18:12 -07001393 @Override
1394 public boolean onTouchEvent(MotionEvent ev) {
Adam Cohen258d9fc2012-10-13 20:24:26 -07001395 if (DISABLE_TOUCH_INTERACTION) {
1396 return false;
1397 }
1398
Jim Millerdcb3d842012-08-23 19:18:12 -07001399 // Skip touch handling if there are no pages to swipe
1400 if (getChildCount() <= 0) return super.onTouchEvent(ev);
1401
1402 acquireVelocityTrackerAndAddMovement(ev);
1403
1404 final int action = ev.getAction();
1405
1406 switch (action & MotionEvent.ACTION_MASK) {
1407 case MotionEvent.ACTION_DOWN:
1408 /*
1409 * If being flinged and user touches, stop the fling. isFinished
1410 * will be false if being flinged.
1411 */
1412 if (!mScroller.isFinished()) {
1413 mScroller.abortAnimation();
1414 }
1415
1416 // Remember where the motion event started
Jim Millercaf24fc2013-09-10 18:37:01 -07001417 saveDownState(ev);
Jim Millerd6523da2012-10-21 16:47:02 -07001418
Jim Millerdcb3d842012-08-23 19:18:12 -07001419 if (mTouchState == TOUCH_STATE_SCROLLING) {
1420 pageBeginMoving();
Jim Millerafef5b22013-10-13 16:49:43 -07001421 } else {
1422 setTouchState(TOUCH_STATE_READY);
Jim Millerc162dd02013-09-25 18:57:43 -07001423 }
1424
Jim Millerafef5b22013-10-13 16:49:43 -07001425 if (mIsCameraEvent) {
Jim Millerc162dd02013-09-25 18:57:43 -07001426 animateWarpPageOnScreen("onTouch(): DOWN");
Jim Millerdcb3d842012-08-23 19:18:12 -07001427 }
1428 break;
1429
1430 case MotionEvent.ACTION_MOVE:
1431 if (mTouchState == TOUCH_STATE_SCROLLING) {
1432 // Scroll to follow the motion event
1433 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
Jim Millerd794e642013-05-22 15:53:24 -07001434
1435 if (pointerIndex == -1) return true;
1436
Jim Millerdcb3d842012-08-23 19:18:12 -07001437 final float x = ev.getX(pointerIndex);
1438 final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1439
1440 mTotalMotionX += Math.abs(deltaX);
1441
1442 // Only scroll and update mLastMotionX if we have moved some discrete amount. We
1443 // keep the remainder because we are actually testing if we've moved from the last
1444 // scrolled position (which is discrete).
1445 if (Math.abs(deltaX) >= 1.0f) {
1446 mTouchX += deltaX;
1447 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
Jim Miller1c058a62013-10-13 19:35:47 -07001448 if (isWarping()) {
1449 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
1450 v.setTranslationX(v.getTranslationX() - deltaX);
1451 } else if (!mDeferScrollUpdate) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001452 scrollBy((int) deltaX, 0);
1453 if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
1454 } else {
1455 invalidate();
1456 }
1457 mLastMotionX = x;
1458 mLastMotionXRemainder = deltaX - (int) deltaX;
1459 } else {
1460 awakenScrollBars();
1461 }
Jim Millerd6523da2012-10-21 16:47:02 -07001462 } else if (mTouchState == TOUCH_STATE_REORDERING) {
1463 // Update the last motion position
1464 mLastMotionX = ev.getX();
1465 mLastMotionY = ev.getY();
1466
Jim Millerb5f3b702012-10-21 19:09:23 -07001467 // Update the parent down so that our zoom animations take this new movement into
Jim Millerd6523da2012-10-21 16:47:02 -07001468 // account
Winson Chungf3b9ec82012-11-01 14:48:51 -07001469 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
Jim Millerd6523da2012-10-21 16:47:02 -07001470 mParentDownMotionX = pt[0];
1471 mParentDownMotionY = pt[1];
1472 updateDragViewTranslationDuringDrag();
1473
1474 // Find the closest page to the touch point
1475 final int dragViewIndex = indexOfChild(mDragView);
Jim Millerb5f3b702012-10-21 19:09:23 -07001476 int bufferSize = (int) (REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE *
Winson Chungefc49252012-10-26 15:41:27 -07001477 getViewportWidth());
Winson Chungf3b9ec82012-11-01 14:48:51 -07001478 int leftBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.left, 0)[0]
Winson Chungefc49252012-10-26 15:41:27 -07001479 + bufferSize);
Winson Chungf3b9ec82012-11-01 14:48:51 -07001480 int rightBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.right, 0)[0]
Winson Chungefc49252012-10-26 15:41:27 -07001481 - bufferSize);
1482
Winson Chungf3b9ec82012-11-01 14:48:51 -07001483 // Change the drag view if we are hovering over the drop target
1484 boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
1485 (int) mParentDownMotionX, (int) mParentDownMotionY);
1486 setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
1487
Winson Chungefc49252012-10-26 15:41:27 -07001488 if (DEBUG) Log.d(TAG, "leftBufferEdge: " + leftBufferEdge);
1489 if (DEBUG) Log.d(TAG, "rightBufferEdge: " + rightBufferEdge);
1490 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
1491 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
1492 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
1493 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
1494
Jim Millerd6523da2012-10-21 16:47:02 -07001495 float parentX = mParentDownMotionX;
Jim Millerb5f3b702012-10-21 19:09:23 -07001496 int pageIndexToSnapTo = -1;
Jim Millerd6523da2012-10-21 16:47:02 -07001497 if (parentX < leftBufferEdge && dragViewIndex > 0) {
1498 pageIndexToSnapTo = dragViewIndex - 1;
1499 } else if (parentX > rightBufferEdge && dragViewIndex < getChildCount() - 1) {
1500 pageIndexToSnapTo = dragViewIndex + 1;
Jim Millerb5f3b702012-10-21 19:09:23 -07001501 }
Jim Millerd6523da2012-10-21 16:47:02 -07001502
1503 final int pageUnderPointIndex = pageIndexToSnapTo;
Winson Chungf3b9ec82012-11-01 14:48:51 -07001504 if (pageUnderPointIndex > -1 && !isHoveringOverDelete) {
Jim Miller19a52672012-10-23 19:52:04 -07001505 mTempVisiblePagesRange[0] = 0;
1506 mTempVisiblePagesRange[1] = getPageCount() - 1;
1507 boundByReorderablePages(true, mTempVisiblePagesRange);
1508 if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
1509 pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
1510 pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
Jim Millerd6523da2012-10-21 16:47:02 -07001511 mSidePageHoverIndex = pageUnderPointIndex;
1512 mSidePageHoverRunnable = new Runnable() {
1513 @Override
1514 public void run() {
1515 // Update the down scroll position to account for the fact that the
1516 // current page is moved
Jim Millerb5f3b702012-10-21 19:09:23 -07001517 mDownScrollX = getChildOffset(pageUnderPointIndex)
Jim Millerd6523da2012-10-21 16:47:02 -07001518 - getRelativeChildOffset(pageUnderPointIndex);
Jim Millerb5f3b702012-10-21 19:09:23 -07001519
Jim Millerd6523da2012-10-21 16:47:02 -07001520 // Setup the scroll to the correct page before we swap the views
1521 snapToPage(pageUnderPointIndex);
Jim Millerb5f3b702012-10-21 19:09:23 -07001522
1523 // For each of the pages between the paged view and the drag view,
1524 // animate them from the previous position to the new position in
Jim Millerd6523da2012-10-21 16:47:02 -07001525 // the layout (as a result of the drag view moving in the layout)
1526 int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
Jim Millerb5f3b702012-10-21 19:09:23 -07001527 int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
Jim Millerd6523da2012-10-21 16:47:02 -07001528 dragViewIndex + 1 : pageUnderPointIndex;
1529 int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
1530 dragViewIndex - 1 : pageUnderPointIndex;
1531 for (int i = lowerIndex; i <= upperIndex; ++i) {
1532 View v = getChildAt(i);
Jim Millerb5f3b702012-10-21 19:09:23 -07001533 // dragViewIndex < pageUnderPointIndex, so after we remove the
1534 // drag view all subsequent views to pageUnderPointIndex will
Jim Millerd6523da2012-10-21 16:47:02 -07001535 // shift down.
Winson Chungefc49252012-10-26 15:41:27 -07001536 int oldX = getViewportOffsetX() + getChildOffset(i);
1537 int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
Jim Millerd6523da2012-10-21 16:47:02 -07001538
1539 // Animate the view translation from its old position to its new
1540 // position
1541 AnimatorSet anim = (AnimatorSet) v.getTag();
1542 if (anim != null) {
1543 anim.cancel();
1544 }
1545
1546 v.setTranslationX(oldX - newX);
1547 anim = new AnimatorSet();
1548 anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
1549 anim.playTogether(
1550 ObjectAnimator.ofFloat(v, "translationX", 0f));
1551 anim.start();
1552 v.setTag(anim);
1553 }
1554
1555 removeView(mDragView);
Michael Jurka75b5cfb2012-11-15 18:22:47 -08001556 onRemoveView(mDragView, false);
Jim Millerd6523da2012-10-21 16:47:02 -07001557 addView(mDragView, pageUnderPointIndex);
Michael Jurka1254f2f2012-10-25 11:44:31 -07001558 onAddView(mDragView, pageUnderPointIndex);
Jim Millerd6523da2012-10-21 16:47:02 -07001559 mSidePageHoverIndex = -1;
Jim Millerb5f3b702012-10-21 19:09:23 -07001560 }
Jim Millerd6523da2012-10-21 16:47:02 -07001561 };
1562 postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
1563 }
1564 } else {
1565 removeCallbacks(mSidePageHoverRunnable);
1566 mSidePageHoverIndex = -1;
1567 }
Jim Millercaf24fc2013-09-10 18:37:01 -07001568 } else if (mIsCameraEvent || determineScrollingStart(ev)) {
1569 startScrolling(ev);
Jim Millerdcb3d842012-08-23 19:18:12 -07001570 }
1571 break;
1572
1573 case MotionEvent.ACTION_UP:
1574 if (mTouchState == TOUCH_STATE_SCROLLING) {
1575 final int activePointerId = mActivePointerId;
1576 final int pointerIndex = ev.findPointerIndex(activePointerId);
Jim Millerfaaa3652013-10-15 13:46:51 -07001577
1578 if (pointerIndex == -1) return true;
1579
Jim Millerdcb3d842012-08-23 19:18:12 -07001580 final float x = ev.getX(pointerIndex);
1581 final VelocityTracker velocityTracker = mVelocityTracker;
1582 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1583 int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1584 final int deltaX = (int) (x - mDownMotionX);
1585 final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
1586 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1587 SIGNIFICANT_MOVE_THRESHOLD;
1588
1589 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1590
1591 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1592 Math.abs(velocityX) > mFlingThresholdVelocity;
1593
1594 // In the case that the page is moved far to one direction and then is flung
1595 // in the opposite direction, we use a threshold to determine whether we should
1596 // just return to the starting page, or if we should skip one further.
1597 boolean returnToOriginalPage = false;
1598 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1599 Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1600 returnToOriginalPage = true;
1601 }
1602
1603 int finalPage;
1604 // We give flings precedence over large moves, which is why we short-circuit our
1605 // test for a large move if a fling has been registered. That is, a large
1606 // move to the left and fling to the right will register as a fling to the right.
1607 if (((isSignificantMove && deltaX > 0 && !isFling) ||
1608 (isFling && velocityX > 0)) && mCurrentPage > 0) {
Jim Millerc162dd02013-09-25 18:57:43 -07001609 finalPage = returnToOriginalPage || isWarping()
1610 ? mCurrentPage : mCurrentPage - 1;
Jim Millerdcb3d842012-08-23 19:18:12 -07001611 snapToPageWithVelocity(finalPage, velocityX);
1612 } else if (((isSignificantMove && deltaX < 0 && !isFling) ||
1613 (isFling && velocityX < 0)) &&
1614 mCurrentPage < getChildCount() - 1) {
1615 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
1616 snapToPageWithVelocity(finalPage, velocityX);
1617 } else {
1618 snapToDestination();
1619 }
1620 } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1621 // at this point we have not moved beyond the touch slop
1622 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1623 // we can just page
1624 int nextPage = Math.max(0, mCurrentPage - 1);
1625 if (nextPage != mCurrentPage) {
1626 snapToPage(nextPage);
1627 } else {
1628 snapToDestination();
1629 }
1630 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1631 // at this point we have not moved beyond the touch slop
1632 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1633 // we can just page
1634 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1635 if (nextPage != mCurrentPage) {
1636 snapToPage(nextPage);
1637 } else {
1638 snapToDestination();
1639 }
Jim Millerd6523da2012-10-21 16:47:02 -07001640 } else if (mTouchState == TOUCH_STATE_REORDERING) {
Winson Chungf3b9ec82012-11-01 14:48:51 -07001641 // Update the last motion position
1642 mLastMotionX = ev.getX();
1643 mLastMotionY = ev.getY();
1644
1645 // Update the parent down so that our zoom animations take this new movement into
1646 // account
1647 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1648 mParentDownMotionX = pt[0];
1649 mParentDownMotionY = pt[1];
1650 updateDragViewTranslationDuringDrag();
1651 boolean handledFling = false;
Jim Millerd6523da2012-10-21 16:47:02 -07001652 if (!DISABLE_FLING_TO_DELETE) {
1653 // Check the velocity and see if we are flinging-to-delete
1654 PointF flingToDeleteVector = isFlingingToDelete();
1655 if (flingToDeleteVector != null) {
1656 onFlingToDelete(flingToDeleteVector);
Winson Chungf3b9ec82012-11-01 14:48:51 -07001657 handledFling = true;
Jim Millerd6523da2012-10-21 16:47:02 -07001658 }
1659 }
Winson Chungf3b9ec82012-11-01 14:48:51 -07001660 if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
1661 (int) mParentDownMotionY)) {
1662 onDropToDelete();
1663 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001664 } else {
Jim Millerafef5b22013-10-13 16:49:43 -07001665 if (DEBUG_WARP) Log.v(TAG, "calling onUnhandledTap()");
1666 if (mWarpPageExposed && !isAnimatingWarpPage()) {
1667 animateWarpPageOffScreen("unhandled tap", true);
1668 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001669 onUnhandledTap(ev);
1670 }
Jim Millerd6523da2012-10-21 16:47:02 -07001671
1672 // Remove the callback to wait for the side page hover timeout
1673 removeCallbacks(mSidePageHoverRunnable);
1674 // End any intermediate reordering states
1675 resetTouchState();
Jim Millerdcb3d842012-08-23 19:18:12 -07001676 break;
1677
1678 case MotionEvent.ACTION_CANCEL:
1679 if (mTouchState == TOUCH_STATE_SCROLLING) {
1680 snapToDestination();
1681 }
Jim Millerd6523da2012-10-21 16:47:02 -07001682 resetTouchState();
Jim Millerdcb3d842012-08-23 19:18:12 -07001683 break;
1684
1685 case MotionEvent.ACTION_POINTER_UP:
1686 onSecondaryPointerUp(ev);
1687 break;
1688 }
1689
1690 return true;
1691 }
1692
Michael Jurka1254f2f2012-10-25 11:44:31 -07001693 //public abstract void onFlingToDelete(View v);
Michael Jurka75b5cfb2012-11-15 18:22:47 -08001694 public abstract void onRemoveView(View v, boolean deletePermanently);
Winson Chung4752e7d2012-11-20 17:06:04 -08001695 public abstract void onRemoveViewAnimationCompleted();
Michael Jurka1254f2f2012-10-25 11:44:31 -07001696 public abstract void onAddView(View v, int index);
1697
Jim Millerd6523da2012-10-21 16:47:02 -07001698 private void resetTouchState() {
1699 releaseVelocityTracker();
1700 endReordering();
Jim Millercaf24fc2013-09-10 18:37:01 -07001701 setTouchState(TOUCH_STATE_REST);
Jim Millerd6523da2012-10-21 16:47:02 -07001702 mActivePointerId = INVALID_POINTER;
1703 mDownEventOnEdge = false;
1704 }
1705
1706 protected void onUnhandledTap(MotionEvent ev) {}
1707
Jim Millerdcb3d842012-08-23 19:18:12 -07001708 @Override
1709 public boolean onGenericMotionEvent(MotionEvent event) {
1710 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1711 switch (event.getAction()) {
1712 case MotionEvent.ACTION_SCROLL: {
1713 // Handle mouse (or ext. device) by shifting the page depending on the scroll
1714 final float vscroll;
1715 final float hscroll;
1716 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1717 vscroll = 0;
1718 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1719 } else {
1720 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1721 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1722 }
1723 if (hscroll != 0 || vscroll != 0) {
1724 if (hscroll > 0 || vscroll > 0) {
1725 scrollRight();
1726 } else {
1727 scrollLeft();
1728 }
1729 return true;
1730 }
1731 }
1732 }
1733 }
1734 return super.onGenericMotionEvent(event);
1735 }
1736
1737 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1738 if (mVelocityTracker == null) {
1739 mVelocityTracker = VelocityTracker.obtain();
1740 }
1741 mVelocityTracker.addMovement(ev);
1742 }
1743
1744 private void releaseVelocityTracker() {
1745 if (mVelocityTracker != null) {
1746 mVelocityTracker.recycle();
1747 mVelocityTracker = null;
1748 }
1749 }
1750
1751 private void onSecondaryPointerUp(MotionEvent ev) {
1752 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1753 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1754 final int pointerId = ev.getPointerId(pointerIndex);
1755 if (pointerId == mActivePointerId) {
1756 // This was our active pointer going up. Choose a new
1757 // active pointer and adjust accordingly.
1758 // TODO: Make this decision more intelligent.
1759 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1760 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1761 mLastMotionY = ev.getY(newPointerIndex);
1762 mLastMotionXRemainder = 0;
1763 mActivePointerId = ev.getPointerId(newPointerIndex);
1764 if (mVelocityTracker != null) {
1765 mVelocityTracker.clear();
1766 }
1767 }
1768 }
1769
Jim Millerdcb3d842012-08-23 19:18:12 -07001770 @Override
1771 public void requestChildFocus(View child, View focused) {
1772 super.requestChildFocus(child, focused);
1773 int page = indexToPage(indexOfChild(child));
1774 if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
1775 snapToPage(page);
1776 }
1777 }
1778
1779 protected int getChildIndexForRelativeOffset(int relativeOffset) {
1780 final int childCount = getChildCount();
1781 int left;
1782 int right;
1783 for (int i = 0; i < childCount; ++i) {
1784 left = getRelativeChildOffset(i);
1785 right = (left + getScaledMeasuredWidth(getPageAt(i)));
1786 if (left <= relativeOffset && relativeOffset <= right) {
1787 return i;
1788 }
1789 }
1790 return -1;
1791 }
1792
1793 protected int getChildWidth(int index) {
1794 // This functions are called enough times that it actually makes a difference in the
1795 // profiler -- so just inline the max() here
1796 final int measuredWidth = getPageAt(index).getMeasuredWidth();
1797 final int minWidth = mMinimumWidth;
1798 return (minWidth > measuredWidth) ? minWidth : measuredWidth;
1799 }
Jim Millerb5f3b702012-10-21 19:09:23 -07001800
Jim Millerd6523da2012-10-21 16:47:02 -07001801 int getPageNearestToPoint(float x) {
1802 int index = 0;
1803 for (int i = 0; i < getChildCount(); ++i) {
1804 if (x < getChildAt(i).getRight() - getScrollX()) {
1805 return index;
1806 } else {
1807 index++;
1808 }
1809 }
Jim Millerb5f3b702012-10-21 19:09:23 -07001810 return Math.min(index, getChildCount() - 1);
Jim Millerd6523da2012-10-21 16:47:02 -07001811 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001812
1813 int getPageNearestToCenterOfScreen() {
1814 int minDistanceFromScreenCenter = Integer.MAX_VALUE;
1815 int minDistanceFromScreenCenterIndex = -1;
Winson Chungefc49252012-10-26 15:41:27 -07001816 int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
Jim Millerdcb3d842012-08-23 19:18:12 -07001817 final int childCount = getChildCount();
1818 for (int i = 0; i < childCount; ++i) {
1819 View layout = (View) getPageAt(i);
1820 int childWidth = getScaledMeasuredWidth(layout);
1821 int halfChildWidth = (childWidth / 2);
Winson Chungefc49252012-10-26 15:41:27 -07001822 int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
Jim Millerdcb3d842012-08-23 19:18:12 -07001823 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1824 if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1825 minDistanceFromScreenCenter = distanceFromScreenCenter;
1826 minDistanceFromScreenCenterIndex = i;
1827 }
1828 }
1829 return minDistanceFromScreenCenterIndex;
1830 }
1831
1832 protected void snapToDestination() {
Jim Miller1c058a62013-10-13 19:35:47 -07001833 final int newPage = getPageNearestToCenterOfScreen();
Jim Millerc162dd02013-09-25 18:57:43 -07001834 if (isWarping()) {
Jim Miller1c058a62013-10-13 19:35:47 -07001835 cancelWarpAnimation("snapToDestination", mCurrentPage != newPage);
Jim Millerc162dd02013-09-25 18:57:43 -07001836 }
Jim Miller1c058a62013-10-13 19:35:47 -07001837 snapToPage(newPage, getPageSnapDuration());
Jim Millerc162dd02013-09-25 18:57:43 -07001838 }
1839
1840 private int getPageSnapDuration() {
1841 return isWarping() ? WARP_SNAP_DURATION : PAGE_SNAP_ANIMATION_DURATION;
Jim Millerdcb3d842012-08-23 19:18:12 -07001842 }
1843
1844 private static class ScrollInterpolator implements Interpolator {
1845 public ScrollInterpolator() {
1846 }
1847
1848 public float getInterpolation(float t) {
1849 t -= 1.0f;
1850 return t*t*t*t*t + 1;
1851 }
1852 }
1853
1854 // We want the duration of the page snap animation to be influenced by the distance that
1855 // the screen has to travel, however, we don't want this duration to be effected in a
1856 // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1857 // of travel has on the overall snap duration.
1858 float distanceInfluenceForSnapDuration(float f) {
1859 f -= 0.5f; // center the values about 0.
1860 f *= 0.3f * Math.PI / 2.0f;
1861 return (float) Math.sin(f);
1862 }
1863
1864 protected void snapToPageWithVelocity(int whichPage, int velocity) {
1865 whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
Winson Chungefc49252012-10-26 15:41:27 -07001866 int halfScreenSize = getViewportWidth() / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -07001867
Jim Millerc162dd02013-09-25 18:57:43 -07001868 if (isWarping()) {
Jim Miller1c058a62013-10-13 19:35:47 -07001869 cancelWarpAnimation("snapToPageWithVelocity", mCurrentPage != whichPage);
Jim Millerc162dd02013-09-25 18:57:43 -07001870 }
1871
Jim Millerdcb3d842012-08-23 19:18:12 -07001872 if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1873 if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
Winson Chungefc49252012-10-26 15:41:27 -07001874 + getViewportWidth() + ", " + getChildWidth(whichPage));
Jim Millerdcb3d842012-08-23 19:18:12 -07001875 final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1876 int delta = newX - mUnboundedScrollX;
1877 int duration = 0;
1878
1879 if (Math.abs(velocity) < mMinFlingVelocity) {
1880 // If the velocity is low enough, then treat this more as an automatic page advance
1881 // as opposed to an apparent physical response to flinging
Jim Millerc162dd02013-09-25 18:57:43 -07001882 snapToPage(whichPage, getPageSnapDuration());
Jim Millerdcb3d842012-08-23 19:18:12 -07001883 return;
1884 }
1885
1886 // Here we compute a "distance" that will be used in the computation of the overall
1887 // snap duration. This is a function of the actual distance that needs to be traveled;
1888 // we keep this value close to half screen size in order to reduce the variance in snap
1889 // duration as a function of the distance the page needs to travel.
1890 float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
1891 float distance = halfScreenSize + halfScreenSize *
1892 distanceInfluenceForSnapDuration(distanceRatio);
1893
1894 velocity = Math.abs(velocity);
1895 velocity = Math.max(mMinSnapVelocity, velocity);
1896
1897 // we want the page's snap velocity to approximately match the velocity at which the
1898 // user flings, so we scale the duration by a value near to the derivative of the scroll
1899 // interpolator at zero, ie. 5. We use 4 to make it a little slower.
1900 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1901
1902 snapToPage(whichPage, delta, duration);
1903 }
1904
1905 protected void snapToPage(int whichPage) {
Jim Millerc162dd02013-09-25 18:57:43 -07001906 snapToPage(whichPage, getPageSnapDuration());
Jim Millerdcb3d842012-08-23 19:18:12 -07001907 }
Jim Miller19a52672012-10-23 19:52:04 -07001908 protected void snapToPageImmediately(int whichPage) {
Jim Millerc162dd02013-09-25 18:57:43 -07001909 snapToPage(whichPage, getPageSnapDuration(), true);
Jim Miller19a52672012-10-23 19:52:04 -07001910 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001911
1912 protected void snapToPage(int whichPage, int duration) {
Jim Miller19a52672012-10-23 19:52:04 -07001913 snapToPage(whichPage, duration, false);
1914 }
1915 protected void snapToPage(int whichPage, int duration, boolean immediate) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001916 whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1917
1918 if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
Winson Chungefc49252012-10-26 15:41:27 -07001919 if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getViewportWidth() + ", "
Jim Millerdcb3d842012-08-23 19:18:12 -07001920 + getChildWidth(whichPage));
1921 int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1922 final int sX = mUnboundedScrollX;
1923 final int delta = newX - sX;
Jim Miller19a52672012-10-23 19:52:04 -07001924 snapToPage(whichPage, delta, duration, immediate);
Jim Millerdcb3d842012-08-23 19:18:12 -07001925 }
1926
1927 protected void snapToPage(int whichPage, int delta, int duration) {
Jim Miller19a52672012-10-23 19:52:04 -07001928 snapToPage(whichPage, delta, duration, false);
1929 }
Jim Millercaf24fc2013-09-10 18:37:01 -07001930
Jim Miller19a52672012-10-23 19:52:04 -07001931 protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001932 if (mPageSwapIndex != -1 && whichPage == mPageSwapIndex) {
Jim Millerf4db8f92013-09-20 14:21:50 -07001933 mNextPage = mPageWarpIndex; // jump to the warp page
Jim Millercaf24fc2013-09-10 18:37:01 -07001934 if (DEBUG_WARP) Log.v(TAG, "snapToPage(" + whichPage + ") : reset mPageSwapIndex");
Jim Millercaf24fc2013-09-10 18:37:01 -07001935 } else {
1936 mNextPage = whichPage;
1937 }
1938
Jim Millerc162dd02013-09-25 18:57:43 -07001939 if (isWarping()) {
John Spurlock56d007b2013-10-28 18:40:56 -04001940 dispatchOnPageEndWarp();
Jim Millerc816b8e2013-11-12 19:52:12 -08001941 notifyPageSwitching(whichPage);
Jim Millerf4db8f92013-09-20 14:21:50 -07001942 resetPageWarp();
Jim Millerc816b8e2013-11-12 19:52:12 -08001943 } else {
1944 notifyPageSwitching(whichPage);
Jim Millerf4db8f92013-09-20 14:21:50 -07001945 }
1946
Jim Millerdcb3d842012-08-23 19:18:12 -07001947 View focusedChild = getFocusedChild();
Svetoslav Ganov45942ca2012-10-31 19:46:24 -07001948 if (focusedChild != null && whichPage != mCurrentPage &&
1949 focusedChild == getPageAt(mCurrentPage)) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001950 focusedChild.clearFocus();
1951 }
1952
1953 pageBeginMoving();
1954 awakenScrollBars(duration);
Jim Miller19a52672012-10-23 19:52:04 -07001955 if (immediate) {
1956 duration = 0;
1957 } else if (duration == 0) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001958 duration = Math.abs(delta);
1959 }
1960
1961 if (!mScroller.isFinished()) mScroller.abortAnimation();
1962 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
1963
John Spurlockbb5c9412012-10-31 09:46:15 -04001964 notifyPageSwitched();
Jim Miller19a52672012-10-23 19:52:04 -07001965
1966 // Trigger a compute() to finish switching pages if necessary
1967 if (immediate) {
1968 computeScroll();
1969 }
1970
Winson Chungf3b9ec82012-11-01 14:48:51 -07001971 mForceScreenScrolled = true;
Jim Millerdcb3d842012-08-23 19:18:12 -07001972 invalidate();
1973 }
1974
Jim Millerc162dd02013-09-25 18:57:43 -07001975 protected boolean isWarping() {
1976 return mPageWarpIndex != -1;
1977 }
1978
Jim Millerdcb3d842012-08-23 19:18:12 -07001979 public void scrollLeft() {
1980 if (mScroller.isFinished()) {
1981 if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
1982 } else {
1983 if (mNextPage > 0) snapToPage(mNextPage - 1);
1984 }
1985 }
1986
1987 public void scrollRight() {
1988 if (mScroller.isFinished()) {
1989 if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
1990 } else {
1991 if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
1992 }
1993 }
1994
1995 public int getPageForView(View v) {
1996 int result = -1;
1997 if (v != null) {
1998 ViewParent vp = v.getParent();
1999 int count = getChildCount();
2000 for (int i = 0; i < count; i++) {
2001 if (vp == getPageAt(i)) {
2002 return i;
2003 }
2004 }
2005 }
2006 return result;
2007 }
2008
Jim Millerdcb3d842012-08-23 19:18:12 -07002009 public static class SavedState extends BaseSavedState {
2010 int currentPage = -1;
2011
2012 SavedState(Parcelable superState) {
2013 super(superState);
2014 }
2015
2016 private SavedState(Parcel in) {
2017 super(in);
2018 currentPage = in.readInt();
2019 }
2020
2021 @Override
2022 public void writeToParcel(Parcel out, int flags) {
2023 super.writeToParcel(out, flags);
2024 out.writeInt(currentPage);
2025 }
2026
2027 public static final Parcelable.Creator<SavedState> CREATOR =
2028 new Parcelable.Creator<SavedState>() {
2029 public SavedState createFromParcel(Parcel in) {
2030 return new SavedState(in);
2031 }
2032
2033 public SavedState[] newArray(int size) {
2034 return new SavedState[size];
2035 }
2036 };
2037 }
2038
2039 protected View getScrollingIndicator() {
2040 return null;
2041 }
2042
2043 protected boolean isScrollingIndicatorEnabled() {
2044 return false;
2045 }
2046
2047 Runnable hideScrollingIndicatorRunnable = new Runnable() {
2048 @Override
2049 public void run() {
2050 hideScrollingIndicator(false);
2051 }
2052 };
2053
2054 protected void flashScrollingIndicator(boolean animated) {
2055 removeCallbacks(hideScrollingIndicatorRunnable);
2056 showScrollingIndicator(!animated);
2057 postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
2058 }
2059
2060 protected void showScrollingIndicator(boolean immediately) {
2061 mShouldShowScrollIndicator = true;
2062 mShouldShowScrollIndicatorImmediately = true;
2063 if (getChildCount() <= 1) return;
2064 if (!isScrollingIndicatorEnabled()) return;
2065
2066 mShouldShowScrollIndicator = false;
2067 getScrollingIndicator();
2068 if (mScrollIndicator != null) {
2069 // Fade the indicator in
2070 updateScrollingIndicatorPosition();
2071 mScrollIndicator.setVisibility(View.VISIBLE);
2072 cancelScrollingIndicatorAnimations();
2073 if (immediately) {
2074 mScrollIndicator.setAlpha(1f);
2075 } else {
2076 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f);
2077 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
2078 mScrollIndicatorAnimator.start();
2079 }
2080 }
2081 }
2082
2083 protected void cancelScrollingIndicatorAnimations() {
2084 if (mScrollIndicatorAnimator != null) {
2085 mScrollIndicatorAnimator.cancel();
2086 }
2087 }
2088
2089 protected void hideScrollingIndicator(boolean immediately) {
2090 if (getChildCount() <= 1) return;
2091 if (!isScrollingIndicatorEnabled()) return;
2092
2093 getScrollingIndicator();
2094 if (mScrollIndicator != null) {
2095 // Fade the indicator out
2096 updateScrollingIndicatorPosition();
2097 cancelScrollingIndicatorAnimations();
2098 if (immediately) {
2099 mScrollIndicator.setVisibility(View.INVISIBLE);
2100 mScrollIndicator.setAlpha(0f);
2101 } else {
2102 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f);
2103 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
2104 mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
2105 private boolean cancelled = false;
2106 @Override
2107 public void onAnimationCancel(android.animation.Animator animation) {
2108 cancelled = true;
2109 }
2110 @Override
2111 public void onAnimationEnd(Animator animation) {
2112 if (!cancelled) {
2113 mScrollIndicator.setVisibility(View.INVISIBLE);
2114 }
2115 }
2116 });
2117 mScrollIndicatorAnimator.start();
2118 }
2119 }
2120 }
2121
2122 /**
2123 * To be overridden by subclasses to determine whether the scroll indicator should stretch to
2124 * fill its space on the track or not.
2125 */
2126 protected boolean hasElasticScrollIndicator() {
2127 return true;
2128 }
2129
2130 private void updateScrollingIndicator() {
2131 if (getChildCount() <= 1) return;
2132 if (!isScrollingIndicatorEnabled()) return;
2133
2134 getScrollingIndicator();
2135 if (mScrollIndicator != null) {
2136 updateScrollingIndicatorPosition();
2137 }
2138 if (mShouldShowScrollIndicator) {
2139 showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
2140 }
2141 }
2142
2143 private void updateScrollingIndicatorPosition() {
2144 if (!isScrollingIndicatorEnabled()) return;
2145 if (mScrollIndicator == null) return;
2146 int numPages = getChildCount();
Winson Chungefc49252012-10-26 15:41:27 -07002147 int pageWidth = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -07002148 int lastChildIndex = Math.max(0, getChildCount() - 1);
2149 int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
2150 int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
2151 int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
2152 mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
2153
2154 float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX));
2155 int indicatorSpace = trackWidth / numPages;
2156 int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
2157 if (hasElasticScrollIndicator()) {
2158 if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
2159 mScrollIndicator.getLayoutParams().width = indicatorSpace;
2160 mScrollIndicator.requestLayout();
2161 }
2162 } else {
2163 int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
2164 indicatorPos += indicatorCenterOffset;
2165 }
2166 mScrollIndicator.setTranslationX(indicatorPos);
2167 }
2168
Jim Millerd6523da2012-10-21 16:47:02 -07002169 // Animate the drag view back to the original position
Winson Chung9dc99232012-10-29 17:43:18 -07002170 void animateDragViewToOriginalPosition() {
Jim Millerd6523da2012-10-21 16:47:02 -07002171 if (mDragView != null) {
2172 AnimatorSet anim = new AnimatorSet();
2173 anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
2174 anim.playTogether(
2175 ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
2176 ObjectAnimator.ofFloat(mDragView, "translationY", 0f));
Winson Chung9dc99232012-10-29 17:43:18 -07002177 anim.addListener(new AnimatorListenerAdapter() {
2178 @Override
2179 public void onAnimationEnd(Animator animation) {
2180 onPostReorderingAnimationCompleted();
2181 }
2182 });
Jim Millerd6523da2012-10-21 16:47:02 -07002183 anim.start();
2184 }
2185 }
2186
2187 // "Zooms out" the PagedView to reveal more side pages
Adam Cohen70009e42012-10-30 16:48:22 -07002188 protected boolean zoomOut() {
Jim Miller19a52672012-10-23 19:52:04 -07002189 if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2190 mZoomInOutAnim.cancel();
2191 }
Jim Millerd6523da2012-10-21 16:47:02 -07002192
Jim Miller19a52672012-10-23 19:52:04 -07002193 if (!(getScaleX() < 1f || getScaleY() < 1f)) {
Jim Millerd6523da2012-10-21 16:47:02 -07002194 mZoomInOutAnim = new AnimatorSet();
2195 mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2196 mZoomInOutAnim.playTogether(
2197 ObjectAnimator.ofFloat(this, "scaleX", mMinScale),
2198 ObjectAnimator.ofFloat(this, "scaleY", mMinScale));
Winson Chungf3b9ec82012-11-01 14:48:51 -07002199 mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2200 @Override
2201 public void onAnimationStart(Animator animation) {
2202 // Show the delete drop target
2203 if (mDeleteDropTarget != null) {
2204 mDeleteDropTarget.setVisibility(View.VISIBLE);
2205 mDeleteDropTarget.animate().alpha(1f)
2206 .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2207 .setListener(new AnimatorListenerAdapter() {
2208 @Override
2209 public void onAnimationStart(Animator animation) {
2210 mDeleteDropTarget.setAlpha(0f);
2211 }
2212 });
2213 }
2214 }
2215 });
Jim Millerd6523da2012-10-21 16:47:02 -07002216 mZoomInOutAnim.start();
2217 return true;
2218 }
2219 return false;
2220 }
2221
2222 protected void onStartReordering() {
Svetoslav Ganovc4842c12012-10-31 14:33:32 -07002223 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2224 announceForAccessibility(mContext.getString(
2225 R.string.keyguard_accessibility_widget_reorder_start));
2226 }
2227
Jim Miller19a52672012-10-23 19:52:04 -07002228 // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
Jim Millercaf24fc2013-09-10 18:37:01 -07002229 setTouchState(TOUCH_STATE_REORDERING);
Jim Miller19a52672012-10-23 19:52:04 -07002230 mIsReordering = true;
2231
Winson Chungf3b9ec82012-11-01 14:48:51 -07002232 // Mark all the non-widget pages as invisible
2233 getVisiblePages(mTempVisiblePagesRange);
2234 boundByReorderablePages(true, mTempVisiblePagesRange);
2235 for (int i = 0; i < getPageCount(); ++i) {
2236 if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2237 getPageAt(i).setAlpha(0f);
2238 }
2239 }
2240
Jim Miller19a52672012-10-23 19:52:04 -07002241 // We must invalidate to trigger a redraw to update the layers such that the drag view
2242 // is always drawn on top
2243 invalidate();
Jim Millerd6523da2012-10-21 16:47:02 -07002244 }
2245
Winson Chung9dc99232012-10-29 17:43:18 -07002246 private void onPostReorderingAnimationCompleted() {
2247 // Trigger the callback when reordering has settled
2248 --mPostReorderingPreZoomInRemainingAnimationCount;
2249 if (mPostReorderingPreZoomInRunnable != null &&
2250 mPostReorderingPreZoomInRemainingAnimationCount == 0) {
2251 mPostReorderingPreZoomInRunnable.run();
2252 mPostReorderingPreZoomInRunnable = null;
2253 }
2254 }
2255
Jim Millerd6523da2012-10-21 16:47:02 -07002256 protected void onEndReordering() {
Svetoslav Ganovc4842c12012-10-31 14:33:32 -07002257 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
Jim Miller1962e262013-09-25 17:08:48 -07002258 if (mDeleteString != null) {
2259 announceForAccessibility(mDeleteString);
2260 mDeleteString = null;
2261 } else {
2262 announceForAccessibility(mContext.getString(
2263 R.string.keyguard_accessibility_widget_reorder_end));
2264 }
Svetoslav Ganovc4842c12012-10-31 14:33:32 -07002265 }
Jim Miller19a52672012-10-23 19:52:04 -07002266 mIsReordering = false;
Winson Chungf3b9ec82012-11-01 14:48:51 -07002267
2268 // Mark all the non-widget pages as visible again
2269 getVisiblePages(mTempVisiblePagesRange);
2270 boundByReorderablePages(true, mTempVisiblePagesRange);
2271 for (int i = 0; i < getPageCount(); ++i) {
2272 if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2273 getPageAt(i).setAlpha(1f);
2274 }
2275 }
Jim Millerd6523da2012-10-21 16:47:02 -07002276 }
Jim Millerb5f3b702012-10-21 19:09:23 -07002277
Jim Miller19a52672012-10-23 19:52:04 -07002278 public boolean startReordering() {
2279 int dragViewIndex = getPageNearestToCenterOfScreen();
2280 mTempVisiblePagesRange[0] = 0;
2281 mTempVisiblePagesRange[1] = getPageCount() - 1;
2282 boundByReorderablePages(true, mTempVisiblePagesRange);
Jim Millerd6523da2012-10-21 16:47:02 -07002283
Jim Miller19a52672012-10-23 19:52:04 -07002284 // Check if we are within the reordering range
2285 if (mTempVisiblePagesRange[0] <= dragViewIndex &&
2286 dragViewIndex <= mTempVisiblePagesRange[1]) {
Adam Cohen3f1c3c72013-10-31 18:40:59 -07002287 mReorderingStarted = true;
Jim Miller19a52672012-10-23 19:52:04 -07002288 if (zoomOut()) {
2289 // Find the drag view under the pointer
2290 mDragView = getChildAt(dragViewIndex);
Jim Millerd6523da2012-10-21 16:47:02 -07002291
Jim Miller19a52672012-10-23 19:52:04 -07002292 onStartReordering();
2293 }
2294 return true;
Jim Millerd6523da2012-10-21 16:47:02 -07002295 }
Jim Miller19a52672012-10-23 19:52:04 -07002296 return false;
Jim Millerd6523da2012-10-21 16:47:02 -07002297 }
2298
Jim Miller19a52672012-10-23 19:52:04 -07002299 boolean isReordering(boolean testTouchState) {
2300 boolean state = mIsReordering;
2301 if (testTouchState) {
2302 state &= (mTouchState == TOUCH_STATE_REORDERING);
2303 }
2304 return state;
2305 }
Jim Millerd6523da2012-10-21 16:47:02 -07002306 void endReordering() {
Jim Miller19a52672012-10-23 19:52:04 -07002307 // For simplicity, we call endReordering sometimes even if reordering was never started.
2308 // In that case, we don't want to do anything.
2309 if (!mReorderingStarted) return;
2310 mReorderingStarted = false;
Jim Miller19a52672012-10-23 19:52:04 -07002311
Jim Millerd6523da2012-10-21 16:47:02 -07002312 // If we haven't flung-to-delete the current child, then we just animate the drag view
2313 // back into position
Winson Chung70aa5282012-10-30 10:56:37 -07002314 final Runnable onCompleteRunnable = new Runnable() {
2315 @Override
2316 public void run() {
2317 onEndReordering();
2318 }
2319 };
Winson Chungf3b9ec82012-11-01 14:48:51 -07002320 if (!mDeferringForDelete) {
Winson Chung9dc99232012-10-29 17:43:18 -07002321 mPostReorderingPreZoomInRunnable = new Runnable() {
2322 public void run() {
Winson Chung9dc99232012-10-29 17:43:18 -07002323 zoomIn(onCompleteRunnable);
2324 };
2325 };
Jim Miller19a52672012-10-23 19:52:04 -07002326
Winson Chung9dc99232012-10-29 17:43:18 -07002327 mPostReorderingPreZoomInRemainingAnimationCount =
2328 NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
2329 // Snap to the current page
2330 snapToPage(indexOfChild(mDragView), 0);
2331 // Animate the drag view back to the front position
2332 animateDragViewToOriginalPosition();
Winson Chung70aa5282012-10-30 10:56:37 -07002333 } else {
Winson Chungf3b9ec82012-11-01 14:48:51 -07002334 // Handled in post-delete-animation-callbacks
Jim Millerd6523da2012-10-21 16:47:02 -07002335 }
2336 }
2337
Jim Millerd6523da2012-10-21 16:47:02 -07002338 // "Zooms in" the PagedView to highlight the current page
Adam Cohen70009e42012-10-30 16:48:22 -07002339 protected boolean zoomIn(final Runnable onCompleteRunnable) {
Jim Miller19a52672012-10-23 19:52:04 -07002340 if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2341 mZoomInOutAnim.cancel();
2342 }
Jim Millerd6523da2012-10-21 16:47:02 -07002343 if (getScaleX() < 1f || getScaleY() < 1f) {
Jim Millerd6523da2012-10-21 16:47:02 -07002344 mZoomInOutAnim = new AnimatorSet();
2345 mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2346 mZoomInOutAnim.playTogether(
2347 ObjectAnimator.ofFloat(this, "scaleX", 1f),
2348 ObjectAnimator.ofFloat(this, "scaleY", 1f));
2349 mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2350 @Override
Winson Chungf3b9ec82012-11-01 14:48:51 -07002351 public void onAnimationStart(Animator animation) {
2352 // Hide the delete drop target
2353 if (mDeleteDropTarget != null) {
2354 mDeleteDropTarget.animate().alpha(0f)
2355 .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2356 .setListener(new AnimatorListenerAdapter() {
2357 @Override
2358 public void onAnimationEnd(Animator animation) {
2359 mDeleteDropTarget.setVisibility(View.GONE);
2360 }
2361 });
2362 }
2363 }
2364 @Override
Jim Millerd6523da2012-10-21 16:47:02 -07002365 public void onAnimationCancel(Animator animation) {
2366 mDragView = null;
2367 }
2368 @Override
2369 public void onAnimationEnd(Animator animation) {
2370 mDragView = null;
2371 if (onCompleteRunnable != null) {
2372 onCompleteRunnable.run();
2373 }
2374 }
2375 });
2376 mZoomInOutAnim.start();
2377 return true;
Jim Miller19a52672012-10-23 19:52:04 -07002378 } else {
2379 if (onCompleteRunnable != null) {
2380 onCompleteRunnable.run();
2381 }
Jim Millerd6523da2012-10-21 16:47:02 -07002382 }
2383 return false;
2384 }
2385
2386 /*
Jim Millerb5f3b702012-10-21 19:09:23 -07002387 * Flinging to delete - IN PROGRESS
Jim Millerd6523da2012-10-21 16:47:02 -07002388 */
2389 private PointF isFlingingToDelete() {
2390 ViewConfiguration config = ViewConfiguration.get(getContext());
2391 mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
2392
2393 if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
2394 // Do a quick dot product test to ensure that we are flinging upwards
2395 PointF vel = new PointF(mVelocityTracker.getXVelocity(),
2396 mVelocityTracker.getYVelocity());
2397 PointF upVec = new PointF(0f, -1f);
2398 float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
2399 (vel.length() * upVec.length()));
2400 if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
2401 return vel;
2402 }
2403 }
2404 return null;
2405 }
2406
2407 /**
2408 * Creates an animation from the current drag view along its current velocity vector.
2409 * For this animation, the alpha runs for a fixed duration and we update the position
2410 * progressively.
2411 */
2412 private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
2413 private View mDragView;
2414 private PointF mVelocity;
2415 private Rect mFrom;
2416 private long mPrevTime;
2417 private float mFriction;
2418
2419 private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
2420
2421 public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
2422 long startTime, float friction) {
2423 mDragView = dragView;
2424 mVelocity = vel;
2425 mFrom = from;
2426 mPrevTime = startTime;
2427 mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
2428 }
2429
2430 @Override
2431 public void onAnimationUpdate(ValueAnimator animation) {
2432 float t = ((Float) animation.getAnimatedValue()).floatValue();
2433 long curTime = AnimationUtils.currentAnimationTimeMillis();
2434
2435 mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
2436 mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
2437
2438 mDragView.setTranslationX(mFrom.left);
2439 mDragView.setTranslationY(mFrom.top);
2440 mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
2441
2442 mVelocity.x *= mFriction;
2443 mVelocity.y *= mFriction;
2444 mPrevTime = curTime;
2445 }
2446 };
2447
Winson Chungf3b9ec82012-11-01 14:48:51 -07002448 private Runnable createPostDeleteAnimationRunnable(final View dragView) {
2449 return new Runnable() {
2450 @Override
2451 public void run() {
2452 int dragViewIndex = indexOfChild(dragView);
2453
2454 // For each of the pages around the drag view, animate them from the previous
2455 // position to the new position in the layout (as a result of the drag view moving
2456 // in the layout)
2457 // NOTE: We can make an assumption here because we have side-bound pages that we
2458 // will always have pages to animate in from the left
2459 getVisiblePages(mTempVisiblePagesRange);
2460 boundByReorderablePages(true, mTempVisiblePagesRange);
2461 boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
2462 boolean slideFromLeft = (isLastWidgetPage ||
2463 dragViewIndex > mTempVisiblePagesRange[0]);
2464
2465 // Setup the scroll to the correct page before we swap the views
2466 if (slideFromLeft) {
2467 snapToPageImmediately(dragViewIndex - 1);
2468 }
2469
2470 int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
2471 int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
2472 int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
2473 int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
2474 ArrayList<Animator> animations = new ArrayList<Animator>();
2475 for (int i = lowerIndex; i <= upperIndex; ++i) {
2476 View v = getChildAt(i);
2477 // dragViewIndex < pageUnderPointIndex, so after we remove the
2478 // drag view all subsequent views to pageUnderPointIndex will
2479 // shift down.
2480 int oldX = 0;
2481 int newX = 0;
2482 if (slideFromLeft) {
2483 if (i == 0) {
2484 // Simulate the page being offscreen with the page spacing
2485 oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
2486 - mPageSpacing;
2487 } else {
2488 oldX = getViewportOffsetX() + getChildOffset(i - 1);
2489 }
2490 newX = getViewportOffsetX() + getChildOffset(i);
2491 } else {
2492 oldX = getChildOffset(i) - getChildOffset(i - 1);
2493 newX = 0;
2494 }
2495
2496 // Animate the view translation from its old position to its new
2497 // position
2498 AnimatorSet anim = (AnimatorSet) v.getTag();
2499 if (anim != null) {
2500 anim.cancel();
2501 }
2502
2503 // Note: Hacky, but we want to skip any optimizations to not draw completely
2504 // hidden views
2505 v.setAlpha(Math.max(v.getAlpha(), 0.01f));
2506 v.setTranslationX(oldX - newX);
2507 anim = new AnimatorSet();
2508 anim.playTogether(
2509 ObjectAnimator.ofFloat(v, "translationX", 0f),
2510 ObjectAnimator.ofFloat(v, "alpha", 1f));
2511 animations.add(anim);
2512 v.setTag(anim);
2513 }
2514
2515 AnimatorSet slideAnimations = new AnimatorSet();
2516 slideAnimations.playTogether(animations);
2517 slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
2518 slideAnimations.addListener(new AnimatorListenerAdapter() {
2519 @Override
2520 public void onAnimationEnd(Animator animation) {
2521 final Runnable onCompleteRunnable = new Runnable() {
2522 @Override
2523 public void run() {
2524 mDeferringForDelete = false;
2525 onEndReordering();
Winson Chung4752e7d2012-11-20 17:06:04 -08002526 onRemoveViewAnimationCompleted();
Winson Chungf3b9ec82012-11-01 14:48:51 -07002527 }
2528 };
2529 zoomIn(onCompleteRunnable);
2530 }
2531 });
2532 slideAnimations.start();
2533
2534 removeView(dragView);
Michael Jurka75b5cfb2012-11-15 18:22:47 -08002535 onRemoveView(dragView, true);
Winson Chungf3b9ec82012-11-01 14:48:51 -07002536 }
2537 };
2538 }
2539
Jim Millerd6523da2012-10-21 16:47:02 -07002540 public void onFlingToDelete(PointF vel) {
Jim Millerd6523da2012-10-21 16:47:02 -07002541 final long startTime = AnimationUtils.currentAnimationTimeMillis();
2542
2543 // NOTE: Because it takes time for the first frame of animation to actually be
2544 // called and we expect the animation to be a continuation of the fling, we have
2545 // to account for the time that has elapsed since the fling finished. And since
2546 // we don't have a startDelay, we will always get call to update when we call
2547 // start() (which we want to ignore).
2548 final TimeInterpolator tInterpolator = new TimeInterpolator() {
2549 private int mCount = -1;
2550 private long mStartTime;
2551 private float mOffset;
2552 /* Anonymous inner class ctor */ {
2553 mStartTime = startTime;
2554 }
2555
2556 @Override
2557 public float getInterpolation(float t) {
2558 if (mCount < 0) {
2559 mCount++;
2560 } else if (mCount == 0) {
2561 mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
2562 mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
2563 mCount++;
2564 }
2565 return Math.min(1f, mOffset + t);
2566 }
2567 };
2568
2569 final Rect from = new Rect();
Jim Millerb5f3b702012-10-21 19:09:23 -07002570 final View dragView = mDragView;
Jim Millerd6523da2012-10-21 16:47:02 -07002571 from.left = (int) dragView.getTranslationX();
2572 from.top = (int) dragView.getTranslationY();
Jim Millerb5f3b702012-10-21 19:09:23 -07002573 AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
2574 from, startTime, FLING_TO_DELETE_FRICTION);
Jim Millerd6523da2012-10-21 16:47:02 -07002575
Jim Miller1962e262013-09-25 17:08:48 -07002576 mDeleteString = getContext().getResources()
2577 .getString(R.string.keyguard_accessibility_widget_deleted,
2578 mDragView.getContentDescription());
Winson Chungf3b9ec82012-11-01 14:48:51 -07002579 final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
Jim Millerd6523da2012-10-21 16:47:02 -07002580
2581 // Create and start the animation
2582 ValueAnimator mDropAnim = new ValueAnimator();
2583 mDropAnim.setInterpolator(tInterpolator);
2584 mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
2585 mDropAnim.setFloatValues(0f, 1f);
2586 mDropAnim.addUpdateListener(updateCb);
2587 mDropAnim.addListener(new AnimatorListenerAdapter() {
2588 public void onAnimationEnd(Animator animation) {
2589 onAnimationEndRunnable.run();
2590 }
2591 });
2592 mDropAnim.start();
Winson Chungf3b9ec82012-11-01 14:48:51 -07002593 mDeferringForDelete = true;
2594 }
2595
2596 /* Drag to delete */
2597 private boolean isHoveringOverDeleteDropTarget(int x, int y) {
2598 if (mDeleteDropTarget != null) {
Winson Chungc065a5d2012-11-07 17:17:33 -08002599 mAltTmpRect.set(0, 0, 0, 0);
2600 View parent = (View) mDeleteDropTarget.getParent();
2601 if (parent != null) {
2602 parent.getGlobalVisibleRect(mAltTmpRect);
2603 }
Winson Chungf3b9ec82012-11-01 14:48:51 -07002604 mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
Winson Chungc065a5d2012-11-07 17:17:33 -08002605 mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
Winson Chungf3b9ec82012-11-01 14:48:51 -07002606 return mTmpRect.contains(x, y);
2607 }
2608 return false;
2609 }
2610
2611 protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
2612
2613 private void onDropToDelete() {
2614 final View dragView = mDragView;
2615
2616 final float toScale = 0f;
2617 final float toAlpha = 0f;
2618
2619 // Create and start the complex animation
2620 ArrayList<Animator> animations = new ArrayList<Animator>();
2621 AnimatorSet motionAnim = new AnimatorSet();
2622 motionAnim.setInterpolator(new DecelerateInterpolator(2));
2623 motionAnim.playTogether(
2624 ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
2625 ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
2626 animations.add(motionAnim);
2627
2628 AnimatorSet alphaAnim = new AnimatorSet();
2629 alphaAnim.setInterpolator(new LinearInterpolator());
2630 alphaAnim.playTogether(
2631 ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
2632 animations.add(alphaAnim);
2633
Jim Miller1962e262013-09-25 17:08:48 -07002634 mDeleteString = getContext().getResources()
2635 .getString(R.string.keyguard_accessibility_widget_deleted,
2636 mDragView.getContentDescription());
Winson Chungf3b9ec82012-11-01 14:48:51 -07002637 final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2638
2639 AnimatorSet anim = new AnimatorSet();
2640 anim.playTogether(animations);
2641 anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
2642 anim.addListener(new AnimatorListenerAdapter() {
2643 public void onAnimationEnd(Animator animation) {
2644 onAnimationEndRunnable.run();
2645 }
2646 });
2647 anim.start();
2648
2649 mDeferringForDelete = true;
Jim Millerd6523da2012-10-21 16:47:02 -07002650 }
2651
Jim Millerdcb3d842012-08-23 19:18:12 -07002652 /* Accessibility */
Jim Millerdcb3d842012-08-23 19:18:12 -07002653 @Override
2654 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2655 super.onInitializeAccessibilityNodeInfo(info);
2656 info.setScrollable(getPageCount() > 1);
2657 if (getCurrentPage() < getPageCount() - 1) {
2658 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2659 }
2660 if (getCurrentPage() > 0) {
2661 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2662 }
2663 }
2664
2665 @Override
2666 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2667 super.onInitializeAccessibilityEvent(event);
2668 event.setScrollable(true);
2669 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2670 event.setFromIndex(mCurrentPage);
2671 event.setToIndex(mCurrentPage);
2672 event.setItemCount(getChildCount());
2673 }
2674 }
2675
2676 @Override
2677 public boolean performAccessibilityAction(int action, Bundle arguments) {
2678 if (super.performAccessibilityAction(action, arguments)) {
2679 return true;
2680 }
2681 switch (action) {
2682 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2683 if (getCurrentPage() < getPageCount() - 1) {
2684 scrollRight();
2685 return true;
2686 }
2687 } break;
2688 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2689 if (getCurrentPage() > 0) {
2690 scrollLeft();
2691 return true;
2692 }
2693 } break;
2694 }
2695 return false;
2696 }
2697
2698 @Override
2699 public boolean onHoverEvent(android.view.MotionEvent event) {
2700 return true;
2701 }
Jim Millercaf24fc2013-09-10 18:37:01 -07002702
2703 void beginCameraEvent() {
2704 mIsCameraEvent = true;
2705 }
2706
2707 void endCameraEvent() {
2708 mIsCameraEvent = false;
2709 }
2710
Jim Millerafef5b22013-10-13 16:49:43 -07002711 AnimatorListenerAdapter mOnScreenAnimationListener = new AnimatorListenerAdapter() {
Jim Millerc162dd02013-09-25 18:57:43 -07002712 @Override
2713 public void onAnimationEnd(Animator animation) {
Jim Millerafef5b22013-10-13 16:49:43 -07002714 mWarpAnimation = null;
2715 if (mTouchState != TOUCH_STATE_SCROLLING && mTouchState != TOUCH_STATE_READY) {
2716 animateWarpPageOffScreen("onScreen end", true);
Jim Millerc162dd02013-09-25 18:57:43 -07002717 }
2718 }
2719 };
2720
Jim Millerafef5b22013-10-13 16:49:43 -07002721 AnimatorListenerAdapter mOffScreenAnimationListener = new AnimatorListenerAdapter() {
2722 @Override
2723 public void onAnimationEnd(Animator animation) {
2724 mWarpAnimation = null;
John Spurlock56d007b2013-10-28 18:40:56 -04002725 mWarpPageExposed = false;
Jim Millerafef5b22013-10-13 16:49:43 -07002726 }
2727 };
2728
Jim Miller1c058a62013-10-13 19:35:47 -07002729 private void cancelWarpAnimation(String msg, boolean abortAnimation) {
Jim Miller6b0afad2013-10-28 19:08:42 -07002730 if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ",abort=" + abortAnimation + ")");
Jim Miller1c058a62013-10-13 19:35:47 -07002731 if (abortAnimation) {
2732 // We're done with the animation and moving to a new page. Let the scroller
2733 // take over the animation.
2734 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
2735 v.animate().cancel();
2736 // Make the scroll amount match the current warp position.
2737 scrollBy(Math.round(-v.getTranslationX()), 0);
2738 v.setTranslationX(0);
2739 } else {
2740 animateWarpPageOffScreen("canceled", true);
2741 }
Jim Millerc162dd02013-09-25 18:57:43 -07002742 }
2743
Jim Millerafef5b22013-10-13 16:49:43 -07002744 private boolean isAnimatingWarpPage() {
2745 return mWarpAnimation != null;
2746 }
2747
Jim Millerc162dd02013-09-25 18:57:43 -07002748 private void animateWarpPageOnScreen(String reason) {
2749 if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")");
John Spurlock56d007b2013-10-28 18:40:56 -04002750 if (isWarping() && !mWarpPageExposed) {
Jim Millerafef5b22013-10-13 16:49:43 -07002751 mWarpPageExposed = true;
John Spurlock56d007b2013-10-28 18:40:56 -04002752 dispatchOnPageBeginWarp();
Jim Millerf4db8f92013-09-20 14:21:50 -07002753 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
2754 if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX());
Jim Millerc162dd02013-09-25 18:57:43 -07002755 DecelerateInterpolator interp = new DecelerateInterpolator(1.5f);
Jim Millerafef5b22013-10-13 16:49:43 -07002756 mWarpAnimation = v.animate();
2757 mWarpAnimation.translationX(mWarpPeekAmount)
Jim Millerc162dd02013-09-25 18:57:43 -07002758 .setInterpolator(interp)
2759 .setDuration(WARP_PEEK_ANIMATION_DURATION)
Jim Millerafef5b22013-10-13 16:49:43 -07002760 .setListener(mOnScreenAnimationListener);
Jim Millerf4db8f92013-09-20 14:21:50 -07002761 }
2762 }
2763
Jim Millerc162dd02013-09-25 18:57:43 -07002764 private void animateWarpPageOffScreen(String reason, boolean animate) {
2765 if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")");
2766 if (isWarping()) {
John Spurlock56d007b2013-10-28 18:40:56 -04002767 dispatchOnPageEndWarp();
Jim Millerf4db8f92013-09-20 14:21:50 -07002768 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex);
2769 if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX());
Jim Millerc162dd02013-09-25 18:57:43 -07002770 AccelerateInterpolator interp = new AccelerateInterpolator(1.5f);
2771 v.animate().translationX(0.0f)
2772 .setInterpolator(interp)
2773 .setDuration(animate ? WARP_PEEK_ANIMATION_DURATION : 0)
Jim Millerafef5b22013-10-13 16:49:43 -07002774 .setListener(mOffScreenAnimationListener);
Jim Millerc162dd02013-09-25 18:57:43 -07002775 } else {
2776 if (DEBUG_WARP) Log.e(TAG, "animateWarpPageOffScreen(): not warping", new Exception());
Jim Millerf4db8f92013-09-20 14:21:50 -07002777 }
2778 }
2779
Jim Millercaf24fc2013-09-10 18:37:01 -07002780 /**
2781 * Swaps the position of the views by setting the left and right edges appropriately.
2782 */
2783 void swapPages(int indexA, int indexB) {
2784 View viewA = getPageAt(indexA);
2785 View viewB = getPageAt(indexB);
2786 if (viewA != viewB && viewA != null && viewB != null) {
2787 int deltaX = viewA.getLeft() - viewB.getLeft();
2788 viewA.offsetLeftAndRight(-deltaX);
2789 viewB.offsetLeftAndRight(deltaX);
2790 }
2791 }
2792
Jim Millerc162dd02013-09-25 18:57:43 -07002793 public void startPageWarp(int pageIndex) {
Jim Millercaf24fc2013-09-10 18:37:01 -07002794 if (DEBUG_WARP) Log.v(TAG, "START WARP");
2795 if (pageIndex != mCurrentPage + 1) {
2796 mPageSwapIndex = mCurrentPage + 1;
2797 }
Jim Millerf4db8f92013-09-20 14:21:50 -07002798 mPageWarpIndex = pageIndex;
2799 }
2800
2801 protected int getPageWarpIndex() {
2802 return mPageWarpIndex;
Jim Millercaf24fc2013-09-10 18:37:01 -07002803 }
2804
Jim Millerc162dd02013-09-25 18:57:43 -07002805 public void stopPageWarp() {
Jim Millercaf24fc2013-09-10 18:37:01 -07002806 if (DEBUG_WARP) Log.v(TAG, "END WARP");
2807 // mPageSwapIndex is reset in snapToPage() after the scroll animation completes
2808 }
2809
2810 public void onPageBeginWarp() {
2811
2812 }
2813
2814 public void onPageEndWarp() {
2815
2816 }
2817
Jim Millerdcb3d842012-08-23 19:18:12 -07002818}