blob: b42a085393bef9049c380fa4488187944b65b82c [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;
Selim Cineked88cee2014-01-10 17:28:15 -080039import android.view.*;
Jim Millerdcb3d842012-08-23 19:18:12 -070040import android.view.accessibility.AccessibilityEvent;
41import android.view.accessibility.AccessibilityManager;
42import android.view.accessibility.AccessibilityNodeInfo;
Jim Millerc162dd02013-09-25 18:57:43 -070043import android.view.animation.AccelerateInterpolator;
Jim Millerd6523da2012-10-21 16:47:02 -070044import android.view.animation.AnimationUtils;
45import android.view.animation.DecelerateInterpolator;
Jim Millerdcb3d842012-08-23 19:18:12 -070046import android.view.animation.Interpolator;
Winson Chungf3b9ec82012-11-01 14:48:51 -070047import android.view.animation.LinearInterpolator;
Jim Millerdcb3d842012-08-23 19:18:12 -070048import android.widget.Scroller;
49
Jim Millerdcb3d842012-08-23 19:18:12 -070050import java.util.ArrayList;
51
52/**
53 * An abstraction of the original Workspace which supports browsing through a
54 * sequential list of "pages"
55 */
Michael Jurka1254f2f2012-10-25 11:44:31 -070056public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
Jim Millerc162dd02013-09-25 18:57:43 -070057 private static final int WARP_SNAP_DURATION = 160;
Jim Millerdcb3d842012-08-23 19:18:12 -070058 private static final String TAG = "WidgetPagedView";
Jorim Jaggi5cf17872014-03-26 18:31:48 +010059 private static final boolean DEBUG = KeyguardConstants.DEBUG;
Jim Millercaf24fc2013-09-10 18:37:01 -070060 private static final boolean DEBUG_WARP = false;
Jim Millerdcb3d842012-08-23 19:18:12 -070061 protected static final int INVALID_PAGE = -1;
Jim Millerc162dd02013-09-25 18:57:43 -070062 private static final int WARP_PEEK_ANIMATION_DURATION = 150;
63 private static final float WARP_ANIMATE_AMOUNT = -75.0f; // in dip
Jim Millerdcb3d842012-08-23 19:18:12 -070064
65 // the min drag distance for a fling to register, to prevent random page shifts
66 private static final int MIN_LENGTH_FOR_FLING = 25;
67
Jim Millerd6523da2012-10-21 16:47:02 -070068 protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
Jim Millerdcb3d842012-08-23 19:18:12 -070069 protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
70 protected static final float NANOTIME_DIV = 1000000000.0f;
71
72 private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
73 private static final float OVERSCROLL_DAMP_FACTOR = 0.14f;
74
75 private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
76 // The page is moved more than halfway, automatically move to the next page on touch up.
Jim Milleraa898472013-11-12 18:14:26 -080077 private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.5f;
Jim Millerdcb3d842012-08-23 19:18:12 -070078
79 // The following constants need to be scaled based on density. The scaled versions will be
80 // assigned to the corresponding member variables below.
Jim Milleraa898472013-11-12 18:14:26 -080081 private static final int FLING_THRESHOLD_VELOCITY = 1500;
Jim Millerdcb3d842012-08-23 19:18:12 -070082 private static final int MIN_SNAP_VELOCITY = 1500;
Jim Milleraa898472013-11-12 18:14:26 -080083 private static final int MIN_FLING_VELOCITY = 500;
Jim Millerdcb3d842012-08-23 19:18:12 -070084
Jim Millerd794e642013-05-22 15:53:24 -070085 // We are disabling touch interaction of the widget region for factory ROM.
Jim Miller838906b2012-10-19 18:41:25 -070086 private static final boolean DISABLE_TOUCH_INTERACTION = false;
Jim Millerd6523da2012-10-21 16:47:02 -070087 private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
88 private static final boolean DISABLE_FLING_TO_DELETE = false;
Adam Cohen258d9fc2012-10-13 20:24:26 -070089
Jim Millerdcb3d842012-08-23 19:18:12 -070090 static final int AUTOMATIC_PAGE_SPACING = -1;
91
92 protected int mFlingThresholdVelocity;
93 protected int mMinFlingVelocity;
94 protected int mMinSnapVelocity;
95
96 protected float mDensity;
97 protected float mSmoothingTime;
98 protected float mTouchX;
99
100 protected boolean mFirstLayout = true;
101
102 protected int mCurrentPage;
Jim Miller0ff7f012012-10-11 20:40:01 -0700103 protected int mChildCountOnLastMeasure;
104
Jim Millerdcb3d842012-08-23 19:18:12 -0700105 protected int mNextPage = INVALID_PAGE;
106 protected int mMaxScrollX;
107 protected Scroller mScroller;
108 private VelocityTracker mVelocityTracker;
109
Jim Millerd6523da2012-10-21 16:47:02 -0700110 private float mParentDownMotionX;
111 private float mParentDownMotionY;
Jim Millerdcb3d842012-08-23 19:18:12 -0700112 private float mDownMotionX;
Jim Millerd6523da2012-10-21 16:47:02 -0700113 private float mDownMotionY;
114 private float mDownScrollX;
Jim Millerdcb3d842012-08-23 19:18:12 -0700115 protected float mLastMotionX;
116 protected float mLastMotionXRemainder;
117 protected float mLastMotionY;
118 protected float mTotalMotionX;
119 private int mLastScreenCenter = -1;
120 private int[] mChildOffsets;
121 private int[] mChildRelativeOffsets;
122 private int[] mChildOffsetsWithLayoutScale;
Jim Miller1962e262013-09-25 17:08:48 -0700123 private String mDeleteString; // Accessibility announcement when widget is deleted
Jim Millerdcb3d842012-08-23 19:18:12 -0700124
125 protected final static int TOUCH_STATE_REST = 0;
126 protected final static int TOUCH_STATE_SCROLLING = 1;
127 protected final static int TOUCH_STATE_PREV_PAGE = 2;
128 protected final static int TOUCH_STATE_NEXT_PAGE = 3;
Jim Millerd6523da2012-10-21 16:47:02 -0700129 protected final static int TOUCH_STATE_REORDERING = 4;
Jim Millerafef5b22013-10-13 16:49:43 -0700130 protected final static int TOUCH_STATE_READY = 5; // when finger is down
Jim Millerd6523da2012-10-21 16:47:02 -0700131
Jim Millerdcb3d842012-08-23 19:18:12 -0700132 protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
Jim Millercaf24fc2013-09-10 18:37:01 -0700133 protected final static float TOUCH_SLOP_SCALE = 1.0f;
Jim Millerdcb3d842012-08-23 19:18:12 -0700134
135 protected int mTouchState = TOUCH_STATE_REST;
136 protected boolean mForceScreenScrolled = false;
137
138 protected OnLongClickListener mLongClickListener;
139
Jim Millerdcb3d842012-08-23 19:18:12 -0700140 protected int mTouchSlop;
141 private int mPagingTouchSlop;
142 private int mMaximumVelocity;
143 private int mMinimumWidth;
144 protected int mPageSpacing;
Jim Millerdcb3d842012-08-23 19:18:12 -0700145 protected int mCellCountX = 0;
146 protected int mCellCountY = 0;
Jim Millerdcb3d842012-08-23 19:18:12 -0700147 protected boolean mAllowOverScroll = true;
148 protected int mUnboundedScrollX;
149 protected int[] mTempVisiblePagesRange = new int[2];
150 protected boolean mForceDrawAllChildrenNextFrame;
151
152 // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
153 // it is equal to the scaled overscroll position. We use a separate value so as to prevent
154 // the screens from continuing to translate beyond the normal bounds.
155 protected int mOverScrollX;
156
157 // parameter that adjusts the layout to be optimized for pages with that scale factor
158 protected float mLayoutScale = 1.0f;
159
160 protected static final int INVALID_POINTER = -1;
161
162 protected int mActivePointerId = INVALID_POINTER;
163
164 private PageSwitchListener mPageSwitchListener;
165
166 protected ArrayList<Boolean> mDirtyPageContent;
167
168 // If true, syncPages and syncPageItems will be called to refresh pages
169 protected boolean mContentIsRefreshable = true;
170
171 // If true, modify alpha of neighboring pages as user scrolls left/right
Jim Millerd6523da2012-10-21 16:47:02 -0700172 protected boolean mFadeInAdjacentScreens = false;
Jim Millerdcb3d842012-08-23 19:18:12 -0700173
174 // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
175 // to switch to a new page
176 protected boolean mUsePagingTouchSlop = true;
177
178 // If true, the subclass should directly update scrollX itself in its computeScroll method
179 // (SmoothPagedView does this)
180 protected boolean mDeferScrollUpdate = false;
181
182 protected boolean mIsPageMoving = false;
183
184 // All syncs and layout passes are deferred until data is ready.
185 protected boolean mIsDataReady = true;
186
187 // Scrolling indicator
188 private ValueAnimator mScrollIndicatorAnimator;
189 private View mScrollIndicator;
190 private int mScrollIndicatorPaddingLeft;
191 private int mScrollIndicatorPaddingRight;
192 private boolean mShouldShowScrollIndicator = false;
193 private boolean mShouldShowScrollIndicatorImmediately = false;
194 protected static final int sScrollIndicatorFadeInDuration = 150;
195 protected static final int sScrollIndicatorFadeOutDuration = 650;
196 protected static final int sScrollIndicatorFlashDuration = 650;
197
Winson Chungefc49252012-10-26 15:41:27 -0700198 // The viewport whether the pages are to be contained (the actual view may be larger than the
199 // viewport)
200 private Rect mViewport = new Rect();
201
Jim Millerd6523da2012-10-21 16:47:02 -0700202 // Reordering
Jim Millerb5f3b702012-10-21 19:09:23 -0700203 // We use the min scale to determine how much to expand the actually PagedView measured
204 // dimensions such that when we are zoomed out, the view is not clipped
Jim Millerd6523da2012-10-21 16:47:02 -0700205 private int REORDERING_DROP_REPOSITION_DURATION = 200;
Winson Chung9dc99232012-10-29 17:43:18 -0700206 protected int REORDERING_REORDER_REPOSITION_DURATION = 300;
Adam Cohenf9048cd2012-10-27 16:36:10 -0700207 protected int REORDERING_ZOOM_IN_OUT_DURATION = 250;
Winson Chung9dc99232012-10-29 17:43:18 -0700208 private int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 300;
Jim Millerd6523da2012-10-21 16:47:02 -0700209 private float REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE = 0.1f;
Winson Chungf3b9ec82012-11-01 14:48:51 -0700210 private long REORDERING_DELETE_DROP_TARGET_FADE_DURATION = 150;
Jim Millerd6523da2012-10-21 16:47:02 -0700211 private float mMinScale = 1f;
212 protected View mDragView;
Winson Chung48275d22012-11-05 10:56:31 -0800213 protected AnimatorSet mZoomInOutAnim;
Jim Millerd6523da2012-10-21 16:47:02 -0700214 private Runnable mSidePageHoverRunnable;
215 private int mSidePageHoverIndex = -1;
Jim Miller19a52672012-10-23 19:52:04 -0700216 // This variable's scope is only for the duration of startReordering() and endReordering()
217 private boolean mReorderingStarted = false;
218 // This variable's scope is for the duration of startReordering() and after the zoomIn()
219 // animation after endReordering()
220 private boolean mIsReordering;
Winson Chung9dc99232012-10-29 17:43:18 -0700221 // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
222 private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
223 private int mPostReorderingPreZoomInRemainingAnimationCount;
224 private Runnable mPostReorderingPreZoomInRunnable;
Jim Millerd6523da2012-10-21 16:47:02 -0700225
226 // Edge swiping
227 private boolean mOnlyAllowEdgeSwipes = false;
228 private boolean mDownEventOnEdge = false;
229 private int mEdgeSwipeRegionSize = 0;
230
231 // Convenience/caching
232 private Matrix mTmpInvMatrix = new Matrix();
233 private float[] mTmpPoint = new float[2];
Winson Chungf3b9ec82012-11-01 14:48:51 -0700234 private Rect mTmpRect = new Rect();
Winson Chungc065a5d2012-11-07 17:17:33 -0800235 private Rect mAltTmpRect = new Rect();
Jim Millerd6523da2012-10-21 16:47:02 -0700236
237 // Fling to delete
238 private int FLING_TO_DELETE_FADE_OUT_DURATION = 350;
239 private float FLING_TO_DELETE_FRICTION = 0.035f;
240 // The degrees specifies how much deviation from the up vector to still consider a fling "up"
Winson Chungf3b9ec82012-11-01 14:48:51 -0700241 private float FLING_TO_DELETE_MAX_FLING_DEGREES = 65f;
Winson Chungefc49252012-10-26 15:41:27 -0700242 protected int mFlingToDeleteThresholdVelocity = -1400;
Winson Chungf3b9ec82012-11-01 14:48:51 -0700243 // Drag to delete
244 private boolean mDeferringForDelete = false;
245 private int DELETE_SLIDE_IN_SIDE_PAGE_DURATION = 250;
246 private int DRAG_TO_DELETE_FADE_OUT_DURATION = 350;
247
248 // Drop to delete
249 private View mDeleteDropTarget;
Jim Millerd6523da2012-10-21 16:47:02 -0700250
Winson Chung48275d22012-11-05 10:56:31 -0800251 // Bouncer
252 private boolean mTopAlignPageWhenShrinkingForBouncer = false;
253
Jim Millercaf24fc2013-09-10 18:37:01 -0700254 // Page warping
Jim Millerf4db8f92013-09-20 14:21:50 -0700255 private int mPageSwapIndex = -1; // the page we swapped out if needed
256 private int mPageWarpIndex = -1; // the page we intend to warp
Selim Cineked88cee2014-01-10 17:28:15 -0800257
Jim Millerafef5b22013-10-13 16:49:43 -0700258 private boolean mWarpPageExposed;
259 private ViewPropertyAnimator mWarpAnimation;
Jim Millerf4db8f92013-09-20 14:21:50 -0700260
Jim Millercaf24fc2013-09-10 18:37:01 -0700261 private boolean mIsCameraEvent;
Jim Millerf4db8f92013-09-20 14:21:50 -0700262 private float mWarpPeekAmount;
John Spurlock56d007b2013-10-28 18:40:56 -0400263 private boolean mOnPageEndWarpCalled;
264 private boolean mOnPageBeginWarpCalled;
Jim Millercaf24fc2013-09-10 18:37:01 -0700265
Jim Millerdcb3d842012-08-23 19:18:12 -0700266 public interface PageSwitchListener {
John Spurlockbb5c9412012-10-31 09:46:15 -0400267 void onPageSwitching(View newPage, int newPageIndex);
268 void onPageSwitched(View newPage, int newPageIndex);
Jim Millerdcb3d842012-08-23 19:18:12 -0700269 }
270
271 public PagedView(Context context) {
272 this(context, null);
273 }
274
275 public PagedView(Context context, AttributeSet attrs) {
276 this(context, attrs, 0);
277 }
278
279 public PagedView(Context context, AttributeSet attrs, int defStyle) {
280 super(context, attrs, defStyle);
281 TypedArray a = context.obtainStyledAttributes(attrs,
282 R.styleable.PagedView, defStyle, 0);
283 setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0));
Jim Millerdcb3d842012-08-23 19:18:12 -0700284 mScrollIndicatorPaddingLeft =
285 a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0);
286 mScrollIndicatorPaddingRight =
Adam Powell0b1b5522012-10-25 13:39:30 -0700287 a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0);
Jim Millerdcb3d842012-08-23 19:18:12 -0700288 a.recycle();
289
Winson Chung48275d22012-11-05 10:56:31 -0800290 Resources r = getResources();
291 mEdgeSwipeRegionSize = r.getDimensionPixelSize(R.dimen.kg_edge_swipe_region_size);
292 mTopAlignPageWhenShrinkingForBouncer =
293 r.getBoolean(R.bool.kg_top_align_page_shrink_on_bouncer_visible);
Adam Powell0b1b5522012-10-25 13:39:30 -0700294
Jim Millerdcb3d842012-08-23 19:18:12 -0700295 setHapticFeedbackEnabled(false);
296 init();
297 }
298
299 /**
300 * Initializes various states for this workspace.
301 */
302 protected void init() {
303 mDirtyPageContent = new ArrayList<Boolean>();
304 mDirtyPageContent.ensureCapacity(32);
305 mScroller = new Scroller(getContext(), new ScrollInterpolator());
306 mCurrentPage = 0;
Jim Millerdcb3d842012-08-23 19:18:12 -0700307
308 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
309 mTouchSlop = configuration.getScaledTouchSlop();
310 mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
311 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
312 mDensity = getResources().getDisplayMetrics().density;
Jim Millerf4db8f92013-09-20 14:21:50 -0700313 mWarpPeekAmount = mDensity * WARP_ANIMATE_AMOUNT;
Jim Millerdcb3d842012-08-23 19:18:12 -0700314
Winson Chungefc49252012-10-26 15:41:27 -0700315 // Scale the fling-to-delete threshold by the density
Jim Millerf4db8f92013-09-20 14:21:50 -0700316 mFlingToDeleteThresholdVelocity = (int) (mFlingToDeleteThresholdVelocity * mDensity);
Winson Chungefc49252012-10-26 15:41:27 -0700317
Jim Millerdcb3d842012-08-23 19:18:12 -0700318 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
319 mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
320 mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
321 setOnHierarchyChangeListener(this);
322 }
323
Winson Chungf3b9ec82012-11-01 14:48:51 -0700324 void setDeleteDropTarget(View v) {
325 mDeleteDropTarget = v;
326 }
327
Jim Millerd6523da2012-10-21 16:47:02 -0700328 // Convenience methods to map points from self to parent and vice versa
Winson Chungf3b9ec82012-11-01 14:48:51 -0700329 float[] mapPointFromViewToParent(View v, float x, float y) {
Jim Millerd6523da2012-10-21 16:47:02 -0700330 mTmpPoint[0] = x;
331 mTmpPoint[1] = y;
Winson Chungf3b9ec82012-11-01 14:48:51 -0700332 v.getMatrix().mapPoints(mTmpPoint);
333 mTmpPoint[0] += v.getLeft();
334 mTmpPoint[1] += v.getTop();
Jim Millerd6523da2012-10-21 16:47:02 -0700335 return mTmpPoint;
336 }
Winson Chungf3b9ec82012-11-01 14:48:51 -0700337 float[] mapPointFromParentToView(View v, float x, float y) {
338 mTmpPoint[0] = x - v.getLeft();
339 mTmpPoint[1] = y - v.getTop();
340 v.getMatrix().invert(mTmpInvMatrix);
Jim Millerd6523da2012-10-21 16:47:02 -0700341 mTmpInvMatrix.mapPoints(mTmpPoint);
342 return mTmpPoint;
343 }
344
345 void updateDragViewTranslationDuringDrag() {
346 float x = mLastMotionX - mDownMotionX + getScrollX() - mDownScrollX;
347 float y = mLastMotionY - mDownMotionY;
348 mDragView.setTranslationX(x);
349 mDragView.setTranslationY(y);
350
351 if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): " + x + ", " + y);
352 }
353
354 public void setMinScale(float f) {
355 mMinScale = f;
356 requestLayout();
357 }
358
359 @Override
360 public void setScaleX(float scaleX) {
361 super.setScaleX(scaleX);
Jim Miller19a52672012-10-23 19:52:04 -0700362 if (isReordering(true)) {
Winson Chungf3b9ec82012-11-01 14:48:51 -0700363 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
Jim Millerd6523da2012-10-21 16:47:02 -0700364 mLastMotionX = p[0];
365 mLastMotionY = p[1];
366 updateDragViewTranslationDuringDrag();
367 }
368 }
369
Jim Millerb5f3b702012-10-21 19:09:23 -0700370 // Convenience methods to get the actual width/height of the PagedView (since it is measured
Jim Millerd6523da2012-10-21 16:47:02 -0700371 // to be larger to account for the minimum possible scale)
Winson Chungefc49252012-10-26 15:41:27 -0700372 int getViewportWidth() {
373 return mViewport.width();
Jim Millerd6523da2012-10-21 16:47:02 -0700374 }
Winson Chungefc49252012-10-26 15:41:27 -0700375 int getViewportHeight() {
376 return mViewport.height();
Jim Millerd6523da2012-10-21 16:47:02 -0700377 }
378
Jim Millerb5f3b702012-10-21 19:09:23 -0700379 // Convenience methods to get the offset ASSUMING that we are centering the pages in the
Jim Millerd6523da2012-10-21 16:47:02 -0700380 // PagedView both horizontally and vertically
Winson Chungefc49252012-10-26 15:41:27 -0700381 int getViewportOffsetX() {
382 return (getMeasuredWidth() - getViewportWidth()) / 2;
Jim Millerd6523da2012-10-21 16:47:02 -0700383 }
Winson Chungefc49252012-10-26 15:41:27 -0700384 int getViewportOffsetY() {
385 return (getMeasuredHeight() - getViewportHeight()) / 2;
Jim Millerd6523da2012-10-21 16:47:02 -0700386 }
387
Jim Millerdcb3d842012-08-23 19:18:12 -0700388 public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
389 mPageSwitchListener = pageSwitchListener;
390 if (mPageSwitchListener != null) {
John Spurlockbb5c9412012-10-31 09:46:15 -0400391 mPageSwitchListener.onPageSwitched(getPageAt(mCurrentPage), mCurrentPage);
Jim Millerdcb3d842012-08-23 19:18:12 -0700392 }
393 }
394
395 /**
396 * Called by subclasses to mark that data is ready, and that we can begin loading and laying
397 * out pages.
398 */
399 protected void setDataIsReady() {
400 mIsDataReady = true;
401 }
402
403 protected boolean isDataReady() {
404 return mIsDataReady;
405 }
406
407 /**
408 * Returns the index of the currently displayed page.
409 *
410 * @return The index of the currently displayed page.
411 */
412 int getCurrentPage() {
413 return mCurrentPage;
414 }
415
416 int getNextPage() {
417 return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
418 }
419
420 int getPageCount() {
421 return getChildCount();
422 }
423
424 View getPageAt(int index) {
425 return getChildAt(index);
426 }
427
428 protected int indexToPage(int index) {
429 return index;
430 }
431
432 /**
433 * Updates the scroll of the current page immediately to its final scroll position. We use this
434 * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
435 * the previous tab page.
436 */
437 protected void updateCurrentPageScroll() {
438 int offset = getChildOffset(mCurrentPage);
439 int relOffset = getRelativeChildOffset(mCurrentPage);
440 int newX = offset - relOffset;
441 scrollTo(newX, 0);
442 mScroller.setFinalX(newX);
443 mScroller.forceFinished(true);
444 }
445
446 /**
447 * Sets the current page.
448 */
449 void setCurrentPage(int currentPage) {
John Spurlockbb5c9412012-10-31 09:46:15 -0400450 notifyPageSwitching(currentPage);
Jim Millerdcb3d842012-08-23 19:18:12 -0700451 if (!mScroller.isFinished()) {
452 mScroller.abortAnimation();
453 }
454 // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
455 // the default
456 if (getChildCount() == 0) {
457 return;
458 }
459
Adam Cohen7e394102012-10-13 19:10:56 -0700460 mForceScreenScrolled = true;
Jim Millerdcb3d842012-08-23 19:18:12 -0700461 mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1));
462 updateCurrentPageScroll();
463 updateScrollingIndicator();
John Spurlockbb5c9412012-10-31 09:46:15 -0400464 notifyPageSwitched();
Jim Millerdcb3d842012-08-23 19:18:12 -0700465 invalidate();
466 }
467
Jim Millerd6523da2012-10-21 16:47:02 -0700468 public void setOnlyAllowEdgeSwipes(boolean enable) {
469 mOnlyAllowEdgeSwipes = enable;
470 }
471
John Spurlockbb5c9412012-10-31 09:46:15 -0400472 protected void notifyPageSwitching(int whichPage) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700473 if (mPageSwitchListener != null) {
John Spurlockbb5c9412012-10-31 09:46:15 -0400474 mPageSwitchListener.onPageSwitching(getPageAt(whichPage), whichPage);
475 }
476 }
477
478 protected void notifyPageSwitched() {
479 if (mPageSwitchListener != null) {
480 mPageSwitchListener.onPageSwitched(getPageAt(mCurrentPage), mCurrentPage);
Jim Millerdcb3d842012-08-23 19:18:12 -0700481 }
482 }
483
484 protected void pageBeginMoving() {
Jim Millercaf24fc2013-09-10 18:37:01 -0700485 if (DEBUG_WARP) Log.v(TAG, "pageBeginMoving(" + mIsPageMoving + ")");
Jim Millerdcb3d842012-08-23 19:18:12 -0700486 if (!mIsPageMoving) {
487 mIsPageMoving = true;
Jim Millerc162dd02013-09-25 18:57:43 -0700488 if (isWarping()) {
John Spurlock56d007b2013-10-28 18:40:56 -0400489 dispatchOnPageBeginWarp();
Jim Millercaf24fc2013-09-10 18:37:01 -0700490 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700491 onPageBeginMoving();
492 }
493 }
494
John Spurlock56d007b2013-10-28 18:40:56 -0400495 private void dispatchOnPageBeginWarp() {
496 if (!mOnPageBeginWarpCalled) {
497 onPageBeginWarp();
498 mOnPageBeginWarpCalled = true;
499 }
500 mOnPageEndWarpCalled = false;
501 }
502
503 private void dispatchOnPageEndWarp() {
504 if (!mOnPageEndWarpCalled) {
505 onPageEndWarp();
506 mOnPageEndWarpCalled = true;
507 }
508 mOnPageBeginWarpCalled = false;
509 }
510
Jim Millerdcb3d842012-08-23 19:18:12 -0700511 protected void pageEndMoving() {
Jim Millercaf24fc2013-09-10 18:37:01 -0700512 if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")");
Jim Millerdcb3d842012-08-23 19:18:12 -0700513 if (mIsPageMoving) {
514 mIsPageMoving = false;
Jim Millerc162dd02013-09-25 18:57:43 -0700515 if (isWarping()) {
John Spurlock56d007b2013-10-28 18:40:56 -0400516 dispatchOnPageEndWarp();
Selim Cineked88cee2014-01-10 17:28:15 -0800517 mWarpPageExposed = false;
Jim Millercaf24fc2013-09-10 18:37:01 -0700518 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700519 onPageEndMoving();
520 }
521 }
522
523 protected boolean isPageMoving() {
524 return mIsPageMoving;
525 }
526
527 // a method that subclasses can override to add behavior
528 protected void onPageBeginMoving() {
529 }
530
531 // a method that subclasses can override to add behavior
532 protected void onPageEndMoving() {
533 }
534
535 /**
536 * Registers the specified listener on each page contained in this workspace.
537 *
538 * @param l The listener used to respond to long clicks.
539 */
540 @Override
541 public void setOnLongClickListener(OnLongClickListener l) {
542 mLongClickListener = l;
543 final int count = getPageCount();
544 for (int i = 0; i < count; i++) {
545 getPageAt(i).setOnLongClickListener(l);
546 }
547 }
548
549 @Override
550 public void scrollBy(int x, int y) {
551 scrollTo(mUnboundedScrollX + x, getScrollY() + y);
552 }
553
554 @Override
555 public void scrollTo(int x, int y) {
556 mUnboundedScrollX = x;
557
558 if (x < 0) {
559 super.scrollTo(0, y);
560 if (mAllowOverScroll) {
561 overScroll(x);
562 }
563 } else if (x > mMaxScrollX) {
564 super.scrollTo(mMaxScrollX, y);
565 if (mAllowOverScroll) {
566 overScroll(x - mMaxScrollX);
567 }
568 } else {
569 mOverScrollX = x;
570 super.scrollTo(x, y);
571 }
572
573 mTouchX = x;
574 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
Jim Millerd6523da2012-10-21 16:47:02 -0700575
576 // Update the last motion events when scrolling
Jim Miller19a52672012-10-23 19:52:04 -0700577 if (isReordering(true)) {
Winson Chungf3b9ec82012-11-01 14:48:51 -0700578 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
Jim Millerd6523da2012-10-21 16:47:02 -0700579 mLastMotionX = p[0];
580 mLastMotionY = p[1];
581 updateDragViewTranslationDuringDrag();
582 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700583 }
584
585 // we moved this functionality to a helper function so SmoothPagedView can reuse it
586 protected boolean computeScrollHelper() {
587 if (mScroller.computeScrollOffset()) {
588 // Don't bother scrolling if the page does not need to be moved
589 if (getScrollX() != mScroller.getCurrX()
590 || getScrollY() != mScroller.getCurrY()
591 || mOverScrollX != mScroller.getCurrX()) {
592 scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
593 }
594 invalidate();
595 return true;
596 } else if (mNextPage != INVALID_PAGE) {
597 mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
598 mNextPage = INVALID_PAGE;
John Spurlockbb5c9412012-10-31 09:46:15 -0400599 notifyPageSwitched();
Jim Millerdcb3d842012-08-23 19:18:12 -0700600
601 // We don't want to trigger a page end moving unless the page has settled
602 // and the user has stopped scrolling
603 if (mTouchState == TOUCH_STATE_REST) {
604 pageEndMoving();
605 }
Winson Chungf3b9ec82012-11-01 14:48:51 -0700606
Winson Chung9dc99232012-10-29 17:43:18 -0700607 onPostReorderingAnimationCompleted();
Jim Millerdcb3d842012-08-23 19:18:12 -0700608 return true;
609 }
610 return false;
611 }
612
Jim Millerdcb3d842012-08-23 19:18:12 -0700613 @Override
614 public void computeScroll() {
615 computeScrollHelper();
616 }
617
Winson Chung6cf53bb2012-11-05 17:55:42 -0800618 protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) {
619 return mTopAlignPageWhenShrinkingForBouncer;
620 }
621
Jim Millerdcb3d842012-08-23 19:18:12 -0700622 @Override
623 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
624 if (!mIsDataReady || getChildCount() == 0) {
625 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
626 return;
627 }
628
Jim Millerd6523da2012-10-21 16:47:02 -0700629 // We measure the dimensions of the PagedView to be larger than the pages so that when we
630 // zoom out (and scale down), the view is still contained in the parent
Winson Chungefc49252012-10-26 15:41:27 -0700631 View parent = (View) getParent();
632 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
633 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
634 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
635 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
636 // NOTE: We multiply by 1.5f to account for the fact that depending on the offset of the
637 // viewport, we can be at most one and a half screens offset once we scale down
Winson Chung6cf53bb2012-11-05 17:55:42 -0800638 DisplayMetrics dm = getResources().getDisplayMetrics();
639 int maxSize = Math.max(dm.widthPixels, dm.heightPixels);
640 int parentWidthSize = (int) (1.5f * maxSize);
641 int parentHeightSize = maxSize;
Winson Chungefc49252012-10-26 15:41:27 -0700642 int scaledWidthSize = (int) (parentWidthSize / mMinScale);
643 int scaledHeightSize = (int) (parentHeightSize / mMinScale);
644 mViewport.set(0, 0, widthSize, heightSize);
Jim Millerdcb3d842012-08-23 19:18:12 -0700645
646 if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
647 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
648 return;
649 }
650
651 // Return early if we aren't given a proper dimension
652 if (widthSize <= 0 || heightSize <= 0) {
653 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
654 return;
655 }
656
657 /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
658 * of the All apps view on XLarge displays to not take up more space then it needs. Width
659 * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
660 * each page to have the same width.
661 */
662 final int verticalPadding = getPaddingTop() + getPaddingBottom();
663 final int horizontalPadding = getPaddingLeft() + getPaddingRight();
664
665 // The children are given the same width and height as the workspace
666 // unless they were set to WRAP_CONTENT
667 if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
Winson Chungefc49252012-10-26 15:41:27 -0700668 if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
669 if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
Jim Millerd6523da2012-10-21 16:47:02 -0700670 if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
Winson Chungefc49252012-10-26 15:41:27 -0700671 if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
Jim Millerdcb3d842012-08-23 19:18:12 -0700672 final int childCount = getChildCount();
673 for (int i = 0; i < childCount; i++) {
674 // disallowing padding in paged view (just pass 0)
675 final View child = getPageAt(i);
Jim Millerd6523da2012-10-21 16:47:02 -0700676 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
Jim Millerdcb3d842012-08-23 19:18:12 -0700677
Jim Millerd6523da2012-10-21 16:47:02 -0700678 int childWidthMode;
679 if (lp.width == LayoutParams.WRAP_CONTENT) {
680 childWidthMode = MeasureSpec.AT_MOST;
681 } else {
682 childWidthMode = MeasureSpec.EXACTLY;
683 }
684
685 int childHeightMode;
686 if (lp.height == LayoutParams.WRAP_CONTENT) {
687 childHeightMode = MeasureSpec.AT_MOST;
688 } else {
689 childHeightMode = MeasureSpec.EXACTLY;
690 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700691
692 final int childWidthMeasureSpec =
693 MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode);
694 final int childHeightMeasureSpec =
695 MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode);
696
697 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
698 }
Jim Millerd6523da2012-10-21 16:47:02 -0700699 setMeasuredDimension(scaledWidthSize, scaledHeightSize);
Jim Millerdcb3d842012-08-23 19:18:12 -0700700
701 // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions.
702 // We also wait until we set the measured dimensions before flushing the cache as well, to
703 // ensure that the cache is filled with good values.
704 invalidateCachedOffsets();
705
Winson Chungf3b9ec82012-11-01 14:48:51 -0700706 if (mChildCountOnLastMeasure != getChildCount() && !mDeferringForDelete) {
Jim Miller0ff7f012012-10-11 20:40:01 -0700707 setCurrentPage(mCurrentPage);
708 }
709 mChildCountOnLastMeasure = getChildCount();
710
Jim Millerdcb3d842012-08-23 19:18:12 -0700711 if (childCount > 0) {
Winson Chungefc49252012-10-26 15:41:27 -0700712 if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getViewportWidth() + ", "
Jim Millerdcb3d842012-08-23 19:18:12 -0700713 + getChildWidth(0));
714
715 // Calculate the variable page spacing if necessary
716 if (mPageSpacing == AUTOMATIC_PAGE_SPACING) {
717 // The gap between pages in the PagedView should be equal to the gap from the page
718 // to the edge of the screen (so it is not visible in the current screen). To
719 // account for unequal padding on each side of the paged view, we take the maximum
720 // of the left/right gap and use that as the gap between each page.
721 int offset = getRelativeChildOffset(0);
722 int spacing = Math.max(offset, widthSize - offset -
723 getChildAt(0).getMeasuredWidth());
724 setPageSpacing(spacing);
725 }
726 }
727
728 updateScrollingIndicatorPosition();
729
730 if (childCount > 0) {
731 mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1);
732 } else {
733 mMaxScrollX = 0;
734 }
735 }
736
737 public void setPageSpacing(int pageSpacing) {
738 mPageSpacing = pageSpacing;
739 invalidateCachedOffsets();
740 }
741
742 @Override
743 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
744 if (!mIsDataReady || getChildCount() == 0) {
745 return;
746 }
747
748 if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
Jim Millerdcb3d842012-08-23 19:18:12 -0700749 final int childCount = getChildCount();
Jim Millerdcb3d842012-08-23 19:18:12 -0700750
Winson Chungefc49252012-10-26 15:41:27 -0700751 int offsetX = getViewportOffsetX();
752 int offsetY = getViewportOffsetY();
Jim Millerd6523da2012-10-21 16:47:02 -0700753
Winson Chungefc49252012-10-26 15:41:27 -0700754 // Update the viewport offsets
755 mViewport.offset(offsetX, offsetY);
756
757 int childLeft = offsetX + getRelativeChildOffset(0);
Jim Millerdcb3d842012-08-23 19:18:12 -0700758 for (int i = 0; i < childCount; i++) {
759 final View child = getPageAt(i);
Winson Chungefc49252012-10-26 15:41:27 -0700760 int childTop = offsetY + getPaddingTop();
Jim Millerdcb3d842012-08-23 19:18:12 -0700761 if (child.getVisibility() != View.GONE) {
762 final int childWidth = getScaledMeasuredWidth(child);
763 final int childHeight = child.getMeasuredHeight();
Jim Millerdcb3d842012-08-23 19:18:12 -0700764
765 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
766 child.layout(childLeft, childTop,
767 childLeft + child.getMeasuredWidth(), childTop + childHeight);
768 childLeft += childWidth + mPageSpacing;
769 }
770 }
771
772 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
773 setHorizontalScrollBarEnabled(false);
774 updateCurrentPageScroll();
775 setHorizontalScrollBarEnabled(true);
776 mFirstLayout = false;
777 }
778 }
779
780 protected void screenScrolled(int screenCenter) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700781 }
782
783 @Override
784 public void onChildViewAdded(View parent, View child) {
785 // This ensures that when children are added, they get the correct transforms / alphas
786 // in accordance with any scroll effects.
787 mForceScreenScrolled = true;
788 invalidate();
789 invalidateCachedOffsets();
790 }
791
792 @Override
793 public void onChildViewRemoved(View parent, View child) {
Jim Millerd6523da2012-10-21 16:47:02 -0700794 mForceScreenScrolled = true;
Jim Millere5fb5e42013-04-10 16:10:06 -0700795 invalidate();
796 invalidateCachedOffsets();
Jim Millerdcb3d842012-08-23 19:18:12 -0700797 }
798
799 protected void invalidateCachedOffsets() {
800 int count = getChildCount();
801 if (count == 0) {
802 mChildOffsets = null;
803 mChildRelativeOffsets = null;
804 mChildOffsetsWithLayoutScale = null;
805 return;
806 }
807
808 mChildOffsets = new int[count];
809 mChildRelativeOffsets = new int[count];
810 mChildOffsetsWithLayoutScale = new int[count];
811 for (int i = 0; i < count; i++) {
812 mChildOffsets[i] = -1;
813 mChildRelativeOffsets[i] = -1;
814 mChildOffsetsWithLayoutScale[i] = -1;
815 }
816 }
817
818 protected int getChildOffset(int index) {
Jim Miller0ff7f012012-10-11 20:40:01 -0700819 if (index < 0 || index > getChildCount() - 1) return 0;
820
Jim Millerdcb3d842012-08-23 19:18:12 -0700821 int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ?
822 mChildOffsets : mChildOffsetsWithLayoutScale;
823
824 if (childOffsets != null && childOffsets[index] != -1) {
825 return childOffsets[index];
826 } else {
827 if (getChildCount() == 0)
828 return 0;
829
830 int offset = getRelativeChildOffset(0);
831 for (int i = 0; i < index; ++i) {
832 offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing;
833 }
834 if (childOffsets != null) {
835 childOffsets[index] = offset;
836 }
837 return offset;
838 }
839 }
840
841 protected int getRelativeChildOffset(int index) {
Jim Miller0ff7f012012-10-11 20:40:01 -0700842 if (index < 0 || index > getChildCount() - 1) return 0;
843
Jim Millerdcb3d842012-08-23 19:18:12 -0700844 if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) {
845 return mChildRelativeOffsets[index];
846 } else {
847 final int padding = getPaddingLeft() + getPaddingRight();
848 final int offset = getPaddingLeft() +
Winson Chungefc49252012-10-26 15:41:27 -0700849 (getViewportWidth() - padding - getChildWidth(index)) / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -0700850 if (mChildRelativeOffsets != null) {
851 mChildRelativeOffsets[index] = offset;
852 }
853 return offset;
854 }
855 }
856
857 protected int getScaledMeasuredWidth(View child) {
858 // This functions are called enough times that it actually makes a difference in the
859 // profiler -- so just inline the max() here
860 final int measuredWidth = child.getMeasuredWidth();
861 final int minWidth = mMinimumWidth;
862 final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth;
863 return (int) (maxWidth * mLayoutScale + 0.5f);
864 }
865
Jim Miller19a52672012-10-23 19:52:04 -0700866 void boundByReorderablePages(boolean isReordering, int[] range) {
Winson Chungefc49252012-10-26 15:41:27 -0700867 // Do nothing
Jim Miller19a52672012-10-23 19:52:04 -0700868 }
869
Jim Millerd6523da2012-10-21 16:47:02 -0700870 // TODO: Fix this
Jim Millerdcb3d842012-08-23 19:18:12 -0700871 protected void getVisiblePages(int[] range) {
Jim Millerd6523da2012-10-21 16:47:02 -0700872 range[0] = 0;
873 range[1] = getPageCount() - 1;
Jim Miller19a52672012-10-23 19:52:04 -0700874
Jim Millerd6523da2012-10-21 16:47:02 -0700875 /*
Jim Millerdcb3d842012-08-23 19:18:12 -0700876 final int pageCount = getChildCount();
877
878 if (pageCount > 0) {
Winson Chungefc49252012-10-26 15:41:27 -0700879 final int screenWidth = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -0700880 int leftScreen = 0;
881 int rightScreen = 0;
Winson Chungefc49252012-10-26 15:41:27 -0700882 int offsetX = getViewportOffsetX() + getScrollX();
Jim Millerdcb3d842012-08-23 19:18:12 -0700883 View currPage = getPageAt(leftScreen);
884 while (leftScreen < pageCount - 1 &&
885 currPage.getX() + currPage.getWidth() -
Jim Millerd6523da2012-10-21 16:47:02 -0700886 currPage.getPaddingRight() < offsetX) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700887 leftScreen++;
888 currPage = getPageAt(leftScreen);
889 }
890 rightScreen = leftScreen;
891 currPage = getPageAt(rightScreen + 1);
892 while (rightScreen < pageCount - 1 &&
Jim Millerd6523da2012-10-21 16:47:02 -0700893 currPage.getX() - currPage.getPaddingLeft() < offsetX + screenWidth) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700894 rightScreen++;
895 currPage = getPageAt(rightScreen + 1);
896 }
Jim Millerd6523da2012-10-21 16:47:02 -0700897
898 // TEMP: this is a hacky way to ensure that animations to new pages are not clipped
899 // because we don't draw them while scrolling?
900 range[0] = Math.max(0, leftScreen - 1);
901 range[1] = Math.min(rightScreen + 1, getChildCount() - 1);
Jim Millerdcb3d842012-08-23 19:18:12 -0700902 } else {
903 range[0] = -1;
904 range[1] = -1;
905 }
Jim Millerd6523da2012-10-21 16:47:02 -0700906 */
Jim Millerdcb3d842012-08-23 19:18:12 -0700907 }
908
909 protected boolean shouldDrawChild(View child) {
910 return child.getAlpha() > 0;
911 }
912
913 @Override
914 protected void dispatchDraw(Canvas canvas) {
Winson Chungefc49252012-10-26 15:41:27 -0700915 int halfScreenSize = getViewportWidth() / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -0700916 // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
917 // Otherwise it is equal to the scaled overscroll position.
918 int screenCenter = mOverScrollX + halfScreenSize;
919
920 if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
921 // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
922 // set it for the next frame
923 mForceScreenScrolled = false;
924 screenScrolled(screenCenter);
925 mLastScreenCenter = screenCenter;
926 }
927
928 // Find out which screens are visible; as an optimization we only call draw on them
929 final int pageCount = getChildCount();
930 if (pageCount > 0) {
931 getVisiblePages(mTempVisiblePagesRange);
932 final int leftScreen = mTempVisiblePagesRange[0];
933 final int rightScreen = mTempVisiblePagesRange[1];
934 if (leftScreen != -1 && rightScreen != -1) {
935 final long drawingTime = getDrawingTime();
936 // Clip to the bounds
937 canvas.save();
938 canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
939 getScrollY() + getBottom() - getTop());
940
Jim Millerd6523da2012-10-21 16:47:02 -0700941 // Draw all the children, leaving the drag view for last
942 for (int i = pageCount - 1; i >= 0; i--) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700943 final View v = getPageAt(i);
Jim Millerd6523da2012-10-21 16:47:02 -0700944 if (v == mDragView) continue;
Jim Millerdcb3d842012-08-23 19:18:12 -0700945 if (mForceDrawAllChildrenNextFrame ||
946 (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
947 drawChild(canvas, v, drawingTime);
948 }
949 }
Jim Millerd6523da2012-10-21 16:47:02 -0700950 // Draw the drag view on top (if there is one)
951 if (mDragView != null) {
952 drawChild(canvas, mDragView, drawingTime);
953 }
954
Jim Millerdcb3d842012-08-23 19:18:12 -0700955 mForceDrawAllChildrenNextFrame = false;
956 canvas.restore();
957 }
958 }
959 }
960
961 @Override
962 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
963 int page = indexToPage(indexOfChild(child));
964 if (page != mCurrentPage || !mScroller.isFinished()) {
965 snapToPage(page);
966 return true;
967 }
968 return false;
969 }
970
971 @Override
972 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
973 int focusablePage;
974 if (mNextPage != INVALID_PAGE) {
975 focusablePage = mNextPage;
976 } else {
977 focusablePage = mCurrentPage;
978 }
979 View v = getPageAt(focusablePage);
980 if (v != null) {
981 return v.requestFocus(direction, previouslyFocusedRect);
982 }
983 return false;
984 }
985
986 @Override
987 public boolean dispatchUnhandledMove(View focused, int direction) {
988 if (direction == View.FOCUS_LEFT) {
989 if (getCurrentPage() > 0) {
990 snapToPage(getCurrentPage() - 1);
991 return true;
992 }
993 } else if (direction == View.FOCUS_RIGHT) {
994 if (getCurrentPage() < getPageCount() - 1) {
995 snapToPage(getCurrentPage() + 1);
996 return true;
997 }
998 }
999 return super.dispatchUnhandledMove(focused, direction);
1000 }
1001
1002 @Override
1003 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1004 if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
1005 getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
1006 }
1007 if (direction == View.FOCUS_LEFT) {
1008 if (mCurrentPage > 0) {
1009 getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
1010 }
1011 } else if (direction == View.FOCUS_RIGHT){
1012 if (mCurrentPage < getPageCount() - 1) {
1013 getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
1014 }
1015 }
1016 }
1017
1018 /**
1019 * If one of our descendant views decides that it could be focused now, only
1020 * pass that along if it's on the current page.
1021 *
1022 * This happens when live folders requery, and if they're off page, they
1023 * end up calling requestFocus, which pulls it on page.
1024 */
1025 @Override
1026 public void focusableViewAvailable(View focused) {
1027 View current = getPageAt(mCurrentPage);
1028 View v = focused;
1029 while (true) {
1030 if (v == current) {
1031 super.focusableViewAvailable(focused);
1032 return;
1033 }
1034 if (v == this) {
1035 return;
1036 }
1037 ViewParent parent = v.getParent();
1038 if (parent instanceof View) {
1039 v = (View)v.getParent();
1040 } else {
1041 return;
1042 }
1043 }
1044 }
1045
1046 /**
Jim Millerdcb3d842012-08-23 19:18:12 -07001047 * Return true if a tap at (x, y) should trigger a flip to the previous page.
1048 */
1049 protected boolean hitsPreviousPage(float x, float y) {
Winson Chungefc49252012-10-26 15:41:27 -07001050 return (x < getViewportOffsetX() + getRelativeChildOffset(mCurrentPage) - mPageSpacing);
Jim Millerdcb3d842012-08-23 19:18:12 -07001051 }
1052
1053 /**
1054 * Return true if a tap at (x, y) should trigger a flip to the next page.
1055 */
1056 protected boolean hitsNextPage(float x, float y) {
Winson Chungefc49252012-10-26 15:41:27 -07001057 return (x > (getViewportOffsetX() + getViewportWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing));
Jim Millerdcb3d842012-08-23 19:18:12 -07001058 }
1059
Winson Chung1272e0e2012-11-26 17:05:37 -08001060 /** Returns whether x and y originated within the buffered viewport */
1061 private boolean isTouchPointInViewportWithBuffer(int x, int y) {
1062 mTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
1063 mViewport.right + mViewport.width() / 2, mViewport.bottom);
1064 return mTmpRect.contains(x, y);
1065 }
1066
1067 /** Returns whether x and y originated within the current page view bounds */
1068 private boolean isTouchPointInCurrentPage(int x, int y) {
1069 View v = getPageAt(getCurrentPage());
1070 if (v != null) {
1071 mTmpRect.set((v.getLeft() - getScrollX()), 0, (v.getRight() - getScrollX()),
1072 v.getBottom());
Winson Chung6cf53bb2012-11-05 17:55:42 -08001073 return mTmpRect.contains(x, y);
Winson Chung6cf53bb2012-11-05 17:55:42 -08001074 }
Winson Chung1272e0e2012-11-26 17:05:37 -08001075 return false;
Winson Chung6cf53bb2012-11-05 17:55:42 -08001076 }
1077
Jim Millerdcb3d842012-08-23 19:18:12 -07001078 @Override
1079 public boolean onInterceptTouchEvent(MotionEvent ev) {
Adam Cohen258d9fc2012-10-13 20:24:26 -07001080 if (DISABLE_TOUCH_INTERACTION) {
1081 return false;
1082 }
1083
Jim Millerdcb3d842012-08-23 19:18:12 -07001084 /*
1085 * This method JUST determines whether we want to intercept the motion.
1086 * If we return true, onTouchEvent will be called and we do the actual
1087 * scrolling there.
1088 */
1089 acquireVelocityTrackerAndAddMovement(ev);
1090
1091 // Skip touch handling if there are no pages to swipe
1092 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
1093
1094 /*
1095 * Shortcut the most recurring case: the user is in the dragging
1096 * state and he is moving his finger. We want to intercept this
1097 * motion.
1098 */
1099 final int action = ev.getAction();
1100 if ((action == MotionEvent.ACTION_MOVE) &&
1101 (mTouchState == TOUCH_STATE_SCROLLING)) {
1102 return true;
1103 }
1104
1105 switch (action & MotionEvent.ACTION_MASK) {
1106 case MotionEvent.ACTION_MOVE: {
1107 /*
1108 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1109 * whether the user has moved far enough from his original down touch.
1110 */
1111 if (mActivePointerId != INVALID_POINTER) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001112 if (mIsCameraEvent || determineScrollingStart(ev)) {
1113 startScrolling(ev);
1114 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001115 break;
1116 }
1117 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
1118 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
1119 // i.e. fall through to the next case (don't break)
1120 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
1121 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
Jim Miller1962e262013-09-25 17:08:48 -07001122
1123 break;
Jim Millerdcb3d842012-08-23 19:18:12 -07001124 }
1125
1126 case MotionEvent.ACTION_DOWN: {
Jim Millerafef5b22013-10-13 16:49:43 -07001127 if (mIsCameraEvent) {
Jim Millerc162dd02013-09-25 18:57:43 -07001128 animateWarpPageOnScreen("interceptTouch(): DOWN");
Jim Millerf4db8f92013-09-20 14:21:50 -07001129 }
Jim Millercaf24fc2013-09-10 18:37:01 -07001130 // Remember where the motion event started
1131 saveDownState(ev);
Jim Millerdcb3d842012-08-23 19:18:12 -07001132
1133 /*
1134 * If being flinged and user touches the screen, initiate drag;
1135 * otherwise don't. mScroller.isFinished should be false when
1136 * being flinged.
1137 */
1138 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
1139 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
1140 if (finishedScrolling) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001141 setTouchState(TOUCH_STATE_REST);
Jim Millerdcb3d842012-08-23 19:18:12 -07001142 mScroller.abortAnimation();
1143 } else {
Jim Millercaf24fc2013-09-10 18:37:01 -07001144 if (mIsCameraEvent || isTouchPointInViewportWithBuffer(
1145 (int) mDownMotionX, (int) mDownMotionY)) {
1146 setTouchState(TOUCH_STATE_SCROLLING);
Winson Chung6cf53bb2012-11-05 17:55:42 -08001147 } else {
Jim Millercaf24fc2013-09-10 18:37:01 -07001148 setTouchState(TOUCH_STATE_REST);
Winson Chung6cf53bb2012-11-05 17:55:42 -08001149 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001150 }
1151
1152 // check if this can be the beginning of a tap on the side of the pages
1153 // to scroll the current page
Jim Millerd6523da2012-10-21 16:47:02 -07001154 if (!DISABLE_TOUCH_SIDE_PAGES) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001155 if (mTouchState != TOUCH_STATE_PREV_PAGE
1156 && mTouchState != TOUCH_STATE_NEXT_PAGE) {
Jim Millerd6523da2012-10-21 16:47:02 -07001157 if (getChildCount() > 0) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001158 float x = ev.getX();
1159 float y = ev.getY();
Jim Millerd6523da2012-10-21 16:47:02 -07001160 if (hitsPreviousPage(x, y)) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001161 setTouchState(TOUCH_STATE_PREV_PAGE);
Jim Millerd6523da2012-10-21 16:47:02 -07001162 } else if (hitsNextPage(x, y)) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001163 setTouchState(TOUCH_STATE_NEXT_PAGE);
Jim Millerd6523da2012-10-21 16:47:02 -07001164 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001165 }
1166 }
1167 }
1168 break;
1169 }
1170
1171 case MotionEvent.ACTION_UP:
1172 case MotionEvent.ACTION_CANCEL:
Jim Millerd6523da2012-10-21 16:47:02 -07001173 resetTouchState();
Winson Chung6cf53bb2012-11-05 17:55:42 -08001174 // Just intercept the touch event on up if we tap outside the strict viewport
Winson Chung1272e0e2012-11-26 17:05:37 -08001175 if (!isTouchPointInCurrentPage((int) mLastMotionX, (int) mLastMotionY)) {
Winson Chung6cf53bb2012-11-05 17:55:42 -08001176 return true;
1177 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001178 break;
1179
1180 case MotionEvent.ACTION_POINTER_UP:
1181 onSecondaryPointerUp(ev);
1182 releaseVelocityTracker();
1183 break;
1184 }
1185
1186 /*
1187 * The only time we want to intercept motion events is if we are in the
1188 * drag mode.
1189 */
1190 return mTouchState != TOUCH_STATE_REST;
1191 }
1192
Jim Millercaf24fc2013-09-10 18:37:01 -07001193 private void setTouchState(int touchState) {
1194 if (mTouchState != touchState) {
Jim Millerafef5b22013-10-13 16:49:43 -07001195 if (DEBUG_WARP) Log.v(TAG, "mTouchState changing to " + touchState);
Jim Millercaf24fc2013-09-10 18:37:01 -07001196 onTouchStateChanged(touchState);
1197 mTouchState = touchState;
1198 }
1199 }
1200
1201 void onTouchStateChanged(int newTouchState) {
1202 if (DEBUG) {
1203 Log.v(TAG, "onTouchStateChanged(old="+ mTouchState + ", new=" + newTouchState + ")");
1204 }
1205 }
1206
1207 /**
1208 * Save the state when we get {@link MotionEvent#ACTION_DOWN}
1209 * @param ev
1210 */
1211 private void saveDownState(MotionEvent ev) {
1212 // Remember where the motion event started
1213 mDownMotionX = mLastMotionX = ev.getX();
1214 mDownMotionY = mLastMotionY = ev.getY();
1215 mDownScrollX = getScrollX();
1216 float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1217 mParentDownMotionX = p[0];
1218 mParentDownMotionY = p[1];
1219 mLastMotionXRemainder = 0;
1220 mTotalMotionX = 0;
1221 mActivePointerId = ev.getPointerId(0);
1222
1223 // Determine if the down event is within the threshold to be an edge swipe
1224 int leftEdgeBoundary = getViewportOffsetX() + mEdgeSwipeRegionSize;
1225 int rightEdgeBoundary = getMeasuredWidth() - getViewportOffsetX() - mEdgeSwipeRegionSize;
1226 if ((mDownMotionX <= leftEdgeBoundary || mDownMotionX >= rightEdgeBoundary)) {
1227 mDownEventOnEdge = true;
1228 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001229 }
1230
Selim Cineked88cee2014-01-10 17:28:15 -08001231 private boolean isHorizontalCameraScroll(MotionEvent ev) {
1232 // Disallow scrolling if we don't have a valid pointer index
1233 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1234 if (pointerIndex == -1) return false;
1235
1236 // If we're only allowing edge swipes, we break out early if the down event wasn't
1237 // at the edge.
1238 if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) return false;
1239
1240 final float x = ev.getX(pointerIndex);
1241 final int xDiff = (int) Math.abs(x - mDownMotionX);
1242
1243 final int touchSlop = Math.round(TOUCH_SLOP_SCALE * mTouchSlop);
1244 boolean xPaged = xDiff > mPagingTouchSlop;
1245 boolean xMoved = xDiff > touchSlop;
1246
1247 return mIsCameraEvent && (mUsePagingTouchSlop ? xPaged : xMoved);
1248 }
1249
Jim Millerdcb3d842012-08-23 19:18:12 -07001250 /*
1251 * Determines if we should change the touch state to start scrolling after the
1252 * user moves their touch point too far.
1253 */
Jim Millercaf24fc2013-09-10 18:37:01 -07001254 protected boolean determineScrollingStart(MotionEvent ev) {
Winson Chung6cf53bb2012-11-05 17:55:42 -08001255 // Disallow scrolling if we don't have a valid pointer index
Jim Millerdcb3d842012-08-23 19:18:12 -07001256 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
Jim Millercaf24fc2013-09-10 18:37:01 -07001257 if (pointerIndex == -1) return false;
Jim Millerd6523da2012-10-21 16:47:02 -07001258
Winson Chung6cf53bb2012-11-05 17:55:42 -08001259 // Disallow scrolling if we started the gesture from outside the viewport
1260 final float x = ev.getX(pointerIndex);
1261 final float y = ev.getY(pointerIndex);
Jim Millercaf24fc2013-09-10 18:37:01 -07001262 if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return false;
Jim Millerd6523da2012-10-21 16:47:02 -07001263
1264 // If we're only allowing edge swipes, we break out early if the down event wasn't
1265 // at the edge.
Jim Millercaf24fc2013-09-10 18:37:01 -07001266 if (mOnlyAllowEdgeSwipes && !mDownEventOnEdge) return false;
Jim Millerd6523da2012-10-21 16:47:02 -07001267
Jim Millerdcb3d842012-08-23 19:18:12 -07001268 final int xDiff = (int) Math.abs(x - mLastMotionX);
Jim Millerdcb3d842012-08-23 19:18:12 -07001269
Jim Millercaf24fc2013-09-10 18:37:01 -07001270 final int touchSlop = Math.round(TOUCH_SLOP_SCALE * mTouchSlop);
Jim Millerdcb3d842012-08-23 19:18:12 -07001271 boolean xPaged = xDiff > mPagingTouchSlop;
1272 boolean xMoved = xDiff > touchSlop;
Jim Millerdcb3d842012-08-23 19:18:12 -07001273
Selim Cineked88cee2014-01-10 17:28:15 -08001274 return mUsePagingTouchSlop ? xPaged : xMoved;
Jim Millercaf24fc2013-09-10 18:37:01 -07001275 }
1276
1277 private void startScrolling(MotionEvent ev) {
1278 // Ignore if we don't have a valid pointer index
1279 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1280 if (pointerIndex == -1) return;
1281
1282 final float x = ev.getX(pointerIndex);
1283 setTouchState(TOUCH_STATE_SCROLLING);
1284 mTotalMotionX += Math.abs(mLastMotionX - x);
1285 mLastMotionX = x;
1286 mLastMotionXRemainder = 0;
1287 mTouchX = getViewportOffsetX() + getScrollX();
1288 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1289 pageBeginMoving();
Jim Millerdcb3d842012-08-23 19:18:12 -07001290 }
1291
Adam Cohen9ec871d2012-10-24 19:25:44 -07001292 protected float getMaxScrollProgress() {
1293 return 1.0f;
1294 }
1295
Adam Cohenf9048cd2012-10-27 16:36:10 -07001296 protected float getBoundedScrollProgress(int screenCenter, View v, int page) {
1297 final int halfScreenSize = getViewportWidth() / 2;
1298
1299 screenCenter = Math.min(mScrollX + halfScreenSize, screenCenter);
1300 screenCenter = Math.max(halfScreenSize, screenCenter);
1301
1302 return getScrollProgress(screenCenter, v, page);
1303 }
1304
Jim Millerdcb3d842012-08-23 19:18:12 -07001305 protected float getScrollProgress(int screenCenter, View v, int page) {
Winson Chungefc49252012-10-26 15:41:27 -07001306 final int halfScreenSize = getViewportWidth() / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -07001307
1308 int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing;
1309 int delta = screenCenter - (getChildOffset(page) -
1310 getRelativeChildOffset(page) + halfScreenSize);
1311
1312 float scrollProgress = delta / (totalDistance * 1.0f);
Adam Cohen9ec871d2012-10-24 19:25:44 -07001313 scrollProgress = Math.min(scrollProgress, getMaxScrollProgress());
1314 scrollProgress = Math.max(scrollProgress, - getMaxScrollProgress());
Jim Millerdcb3d842012-08-23 19:18:12 -07001315 return scrollProgress;
1316 }
1317
1318 // This curve determines how the effect of scrolling over the limits of the page dimishes
1319 // as the user pulls further and further from the bounds
1320 private float overScrollInfluenceCurve(float f) {
1321 f -= 1.0f;
1322 return f * f * f + 1.0f;
1323 }
1324
1325 protected void acceleratedOverScroll(float amount) {
Winson Chungefc49252012-10-26 15:41:27 -07001326 int screenSize = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -07001327
1328 // We want to reach the max over scroll effect when the user has
1329 // over scrolled half the size of the screen
1330 float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1331
1332 if (f == 0) return;
1333
1334 // Clamp this factor, f, to -1 < f < 1
1335 if (Math.abs(f) >= 1) {
1336 f /= Math.abs(f);
1337 }
1338
1339 int overScrollAmount = (int) Math.round(f * screenSize);
1340 if (amount < 0) {
1341 mOverScrollX = overScrollAmount;
1342 super.scrollTo(0, getScrollY());
1343 } else {
1344 mOverScrollX = mMaxScrollX + overScrollAmount;
1345 super.scrollTo(mMaxScrollX, getScrollY());
1346 }
1347 invalidate();
1348 }
1349
1350 protected void dampedOverScroll(float amount) {
Winson Chungefc49252012-10-26 15:41:27 -07001351 int screenSize = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -07001352
1353 float f = (amount / screenSize);
1354
1355 if (f == 0) return;
1356 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1357
1358 // Clamp this factor, f, to -1 < f < 1
1359 if (Math.abs(f) >= 1) {
1360 f /= Math.abs(f);
1361 }
1362
1363 int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
1364 if (amount < 0) {
1365 mOverScrollX = overScrollAmount;
1366 super.scrollTo(0, getScrollY());
1367 } else {
1368 mOverScrollX = mMaxScrollX + overScrollAmount;
1369 super.scrollTo(mMaxScrollX, getScrollY());
1370 }
1371 invalidate();
1372 }
1373
1374 protected void overScroll(float amount) {
1375 dampedOverScroll(amount);
1376 }
1377
1378 protected float maxOverScroll() {
1379 // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1380 // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1381 float f = 1.0f;
1382 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1383 return OVERSCROLL_DAMP_FACTOR * f;
1384 }
Jim Millerb5f3b702012-10-21 19:09:23 -07001385
Jim Millerdcb3d842012-08-23 19:18:12 -07001386 @Override
1387 public boolean onTouchEvent(MotionEvent ev) {
Adam Cohen258d9fc2012-10-13 20:24:26 -07001388 if (DISABLE_TOUCH_INTERACTION) {
1389 return false;
1390 }
1391
Jim Millerdcb3d842012-08-23 19:18:12 -07001392 // Skip touch handling if there are no pages to swipe
1393 if (getChildCount() <= 0) return super.onTouchEvent(ev);
1394
1395 acquireVelocityTrackerAndAddMovement(ev);
1396
1397 final int action = ev.getAction();
1398
1399 switch (action & MotionEvent.ACTION_MASK) {
1400 case MotionEvent.ACTION_DOWN:
1401 /*
1402 * If being flinged and user touches, stop the fling. isFinished
1403 * will be false if being flinged.
1404 */
1405 if (!mScroller.isFinished()) {
1406 mScroller.abortAnimation();
1407 }
1408
1409 // Remember where the motion event started
Jim Millercaf24fc2013-09-10 18:37:01 -07001410 saveDownState(ev);
Jim Millerd6523da2012-10-21 16:47:02 -07001411
Jim Millerdcb3d842012-08-23 19:18:12 -07001412 if (mTouchState == TOUCH_STATE_SCROLLING) {
1413 pageBeginMoving();
Jim Millerafef5b22013-10-13 16:49:43 -07001414 } else {
1415 setTouchState(TOUCH_STATE_READY);
Jim Millerc162dd02013-09-25 18:57:43 -07001416 }
1417
Jim Millerafef5b22013-10-13 16:49:43 -07001418 if (mIsCameraEvent) {
Jim Millerc162dd02013-09-25 18:57:43 -07001419 animateWarpPageOnScreen("onTouch(): DOWN");
Jim Millerdcb3d842012-08-23 19:18:12 -07001420 }
1421 break;
1422
1423 case MotionEvent.ACTION_MOVE:
1424 if (mTouchState == TOUCH_STATE_SCROLLING) {
1425 // Scroll to follow the motion event
1426 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
Jim Millerd794e642013-05-22 15:53:24 -07001427
1428 if (pointerIndex == -1) return true;
1429
Jim Millerdcb3d842012-08-23 19:18:12 -07001430 final float x = ev.getX(pointerIndex);
1431 final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1432
1433 mTotalMotionX += Math.abs(deltaX);
1434
1435 // Only scroll and update mLastMotionX if we have moved some discrete amount. We
1436 // keep the remainder because we are actually testing if we've moved from the last
1437 // scrolled position (which is discrete).
1438 if (Math.abs(deltaX) >= 1.0f) {
1439 mTouchX += deltaX;
1440 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
Jim Miller1c058a62013-10-13 19:35:47 -07001441 if (isWarping()) {
Selim Cineked88cee2014-01-10 17:28:15 -08001442 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
Jim Miller1c058a62013-10-13 19:35:47 -07001443 v.setTranslationX(v.getTranslationX() - deltaX);
1444 } else if (!mDeferScrollUpdate) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001445 scrollBy((int) deltaX, 0);
1446 if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
1447 } else {
1448 invalidate();
1449 }
1450 mLastMotionX = x;
1451 mLastMotionXRemainder = deltaX - (int) deltaX;
1452 } else {
1453 awakenScrollBars();
1454 }
Jim Millerd6523da2012-10-21 16:47:02 -07001455 } else if (mTouchState == TOUCH_STATE_REORDERING) {
1456 // Update the last motion position
1457 mLastMotionX = ev.getX();
1458 mLastMotionY = ev.getY();
1459
Jim Millerb5f3b702012-10-21 19:09:23 -07001460 // Update the parent down so that our zoom animations take this new movement into
Jim Millerd6523da2012-10-21 16:47:02 -07001461 // account
Winson Chungf3b9ec82012-11-01 14:48:51 -07001462 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
Jim Millerd6523da2012-10-21 16:47:02 -07001463 mParentDownMotionX = pt[0];
1464 mParentDownMotionY = pt[1];
1465 updateDragViewTranslationDuringDrag();
1466
1467 // Find the closest page to the touch point
1468 final int dragViewIndex = indexOfChild(mDragView);
Jim Millerb5f3b702012-10-21 19:09:23 -07001469 int bufferSize = (int) (REORDERING_SIDE_PAGE_BUFFER_PERCENTAGE *
Winson Chungefc49252012-10-26 15:41:27 -07001470 getViewportWidth());
Winson Chungf3b9ec82012-11-01 14:48:51 -07001471 int leftBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.left, 0)[0]
Winson Chungefc49252012-10-26 15:41:27 -07001472 + bufferSize);
Winson Chungf3b9ec82012-11-01 14:48:51 -07001473 int rightBufferEdge = (int) (mapPointFromViewToParent(this, mViewport.right, 0)[0]
Winson Chungefc49252012-10-26 15:41:27 -07001474 - bufferSize);
1475
Winson Chungf3b9ec82012-11-01 14:48:51 -07001476 // Change the drag view if we are hovering over the drop target
1477 boolean isHoveringOverDelete = isHoveringOverDeleteDropTarget(
1478 (int) mParentDownMotionX, (int) mParentDownMotionY);
1479 setPageHoveringOverDeleteDropTarget(dragViewIndex, isHoveringOverDelete);
1480
Winson Chungefc49252012-10-26 15:41:27 -07001481 if (DEBUG) Log.d(TAG, "leftBufferEdge: " + leftBufferEdge);
1482 if (DEBUG) Log.d(TAG, "rightBufferEdge: " + rightBufferEdge);
1483 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
1484 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
1485 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
1486 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
1487
Jim Millerd6523da2012-10-21 16:47:02 -07001488 float parentX = mParentDownMotionX;
Jim Millerb5f3b702012-10-21 19:09:23 -07001489 int pageIndexToSnapTo = -1;
Jim Millerd6523da2012-10-21 16:47:02 -07001490 if (parentX < leftBufferEdge && dragViewIndex > 0) {
1491 pageIndexToSnapTo = dragViewIndex - 1;
1492 } else if (parentX > rightBufferEdge && dragViewIndex < getChildCount() - 1) {
1493 pageIndexToSnapTo = dragViewIndex + 1;
Jim Millerb5f3b702012-10-21 19:09:23 -07001494 }
Jim Millerd6523da2012-10-21 16:47:02 -07001495
1496 final int pageUnderPointIndex = pageIndexToSnapTo;
Winson Chungf3b9ec82012-11-01 14:48:51 -07001497 if (pageUnderPointIndex > -1 && !isHoveringOverDelete) {
Jim Miller19a52672012-10-23 19:52:04 -07001498 mTempVisiblePagesRange[0] = 0;
1499 mTempVisiblePagesRange[1] = getPageCount() - 1;
1500 boundByReorderablePages(true, mTempVisiblePagesRange);
1501 if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
1502 pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
1503 pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
Jim Millerd6523da2012-10-21 16:47:02 -07001504 mSidePageHoverIndex = pageUnderPointIndex;
1505 mSidePageHoverRunnable = new Runnable() {
1506 @Override
1507 public void run() {
1508 // Update the down scroll position to account for the fact that the
1509 // current page is moved
Jim Millerb5f3b702012-10-21 19:09:23 -07001510 mDownScrollX = getChildOffset(pageUnderPointIndex)
Jim Millerd6523da2012-10-21 16:47:02 -07001511 - getRelativeChildOffset(pageUnderPointIndex);
Jim Millerb5f3b702012-10-21 19:09:23 -07001512
Jim Millerd6523da2012-10-21 16:47:02 -07001513 // Setup the scroll to the correct page before we swap the views
1514 snapToPage(pageUnderPointIndex);
Jim Millerb5f3b702012-10-21 19:09:23 -07001515
1516 // For each of the pages between the paged view and the drag view,
1517 // animate them from the previous position to the new position in
Jim Millerd6523da2012-10-21 16:47:02 -07001518 // the layout (as a result of the drag view moving in the layout)
1519 int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
Jim Millerb5f3b702012-10-21 19:09:23 -07001520 int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
Jim Millerd6523da2012-10-21 16:47:02 -07001521 dragViewIndex + 1 : pageUnderPointIndex;
1522 int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
1523 dragViewIndex - 1 : pageUnderPointIndex;
1524 for (int i = lowerIndex; i <= upperIndex; ++i) {
1525 View v = getChildAt(i);
Jim Millerb5f3b702012-10-21 19:09:23 -07001526 // dragViewIndex < pageUnderPointIndex, so after we remove the
1527 // drag view all subsequent views to pageUnderPointIndex will
Jim Millerd6523da2012-10-21 16:47:02 -07001528 // shift down.
Winson Chungefc49252012-10-26 15:41:27 -07001529 int oldX = getViewportOffsetX() + getChildOffset(i);
1530 int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
Jim Millerd6523da2012-10-21 16:47:02 -07001531
1532 // Animate the view translation from its old position to its new
1533 // position
1534 AnimatorSet anim = (AnimatorSet) v.getTag();
1535 if (anim != null) {
1536 anim.cancel();
1537 }
1538
1539 v.setTranslationX(oldX - newX);
1540 anim = new AnimatorSet();
1541 anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
1542 anim.playTogether(
1543 ObjectAnimator.ofFloat(v, "translationX", 0f));
1544 anim.start();
1545 v.setTag(anim);
1546 }
1547
1548 removeView(mDragView);
Michael Jurka75b5cfb2012-11-15 18:22:47 -08001549 onRemoveView(mDragView, false);
Jim Millerd6523da2012-10-21 16:47:02 -07001550 addView(mDragView, pageUnderPointIndex);
Michael Jurka1254f2f2012-10-25 11:44:31 -07001551 onAddView(mDragView, pageUnderPointIndex);
Jim Millerd6523da2012-10-21 16:47:02 -07001552 mSidePageHoverIndex = -1;
Jim Millerb5f3b702012-10-21 19:09:23 -07001553 }
Jim Millerd6523da2012-10-21 16:47:02 -07001554 };
1555 postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
1556 }
1557 } else {
1558 removeCallbacks(mSidePageHoverRunnable);
1559 mSidePageHoverIndex = -1;
1560 }
Selim Cineked88cee2014-01-10 17:28:15 -08001561 } else if (determineScrollingStart(ev)) {
Jim Millercaf24fc2013-09-10 18:37:01 -07001562 startScrolling(ev);
Selim Cineked88cee2014-01-10 17:28:15 -08001563 } else if (isHorizontalCameraScroll(ev)) {
1564 startScrolling(ev);
1565 // we need to cancel the camera animation
1566 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
1567 v.animate().cancel();
Jim Millerdcb3d842012-08-23 19:18:12 -07001568 }
1569 break;
1570
1571 case MotionEvent.ACTION_UP:
1572 if (mTouchState == TOUCH_STATE_SCROLLING) {
1573 final int activePointerId = mActivePointerId;
1574 final int pointerIndex = ev.findPointerIndex(activePointerId);
Jim Millerfaaa3652013-10-15 13:46:51 -07001575
1576 if (pointerIndex == -1) return true;
1577
Jim Millerdcb3d842012-08-23 19:18:12 -07001578 final float x = ev.getX(pointerIndex);
1579 final VelocityTracker velocityTracker = mVelocityTracker;
1580 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1581 int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1582 final int deltaX = (int) (x - mDownMotionX);
1583 final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage));
1584 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1585 SIGNIFICANT_MOVE_THRESHOLD;
1586
1587 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1588
1589 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1590 Math.abs(velocityX) > mFlingThresholdVelocity;
1591
1592 // In the case that the page is moved far to one direction and then is flung
1593 // in the opposite direction, we use a threshold to determine whether we should
1594 // just return to the starting page, or if we should skip one further.
1595 boolean returnToOriginalPage = false;
1596 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1597 Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1598 returnToOriginalPage = true;
1599 }
1600
1601 int finalPage;
1602 // We give flings precedence over large moves, which is why we short-circuit our
1603 // test for a large move if a fling has been registered. That is, a large
1604 // move to the left and fling to the right will register as a fling to the right.
1605 if (((isSignificantMove && deltaX > 0 && !isFling) ||
1606 (isFling && velocityX > 0)) && mCurrentPage > 0) {
Jim Millerc162dd02013-09-25 18:57:43 -07001607 finalPage = returnToOriginalPage || isWarping()
1608 ? mCurrentPage : mCurrentPage - 1;
Jim Millerdcb3d842012-08-23 19:18:12 -07001609 snapToPageWithVelocity(finalPage, velocityX);
1610 } else if (((isSignificantMove && deltaX < 0 && !isFling) ||
1611 (isFling && velocityX < 0)) &&
1612 mCurrentPage < getChildCount() - 1) {
Selim Cineked88cee2014-01-10 17:28:15 -08001613 finalPage = returnToOriginalPage ? mCurrentPage :
1614 isWarping() ? getPageWarpIndex() : mCurrentPage + 1;
Jim Millerdcb3d842012-08-23 19:18:12 -07001615 snapToPageWithVelocity(finalPage, velocityX);
1616 } else {
1617 snapToDestination();
1618 }
1619 } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1620 // at this point we have not moved beyond the touch slop
1621 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1622 // we can just page
1623 int nextPage = Math.max(0, mCurrentPage - 1);
1624 if (nextPage != mCurrentPage) {
1625 snapToPage(nextPage);
1626 } else {
1627 snapToDestination();
1628 }
1629 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1630 // at this point we have not moved beyond the touch slop
1631 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1632 // we can just page
1633 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1634 if (nextPage != mCurrentPage) {
1635 snapToPage(nextPage);
1636 } else {
1637 snapToDestination();
1638 }
Jim Millerd6523da2012-10-21 16:47:02 -07001639 } else if (mTouchState == TOUCH_STATE_REORDERING) {
Winson Chungf3b9ec82012-11-01 14:48:51 -07001640 // Update the last motion position
1641 mLastMotionX = ev.getX();
1642 mLastMotionY = ev.getY();
1643
1644 // Update the parent down so that our zoom animations take this new movement into
1645 // account
1646 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1647 mParentDownMotionX = pt[0];
1648 mParentDownMotionY = pt[1];
1649 updateDragViewTranslationDuringDrag();
1650 boolean handledFling = false;
Jim Millerd6523da2012-10-21 16:47:02 -07001651 if (!DISABLE_FLING_TO_DELETE) {
1652 // Check the velocity and see if we are flinging-to-delete
1653 PointF flingToDeleteVector = isFlingingToDelete();
1654 if (flingToDeleteVector != null) {
1655 onFlingToDelete(flingToDeleteVector);
Winson Chungf3b9ec82012-11-01 14:48:51 -07001656 handledFling = true;
Jim Millerd6523da2012-10-21 16:47:02 -07001657 }
1658 }
Winson Chungf3b9ec82012-11-01 14:48:51 -07001659 if (!handledFling && isHoveringOverDeleteDropTarget((int) mParentDownMotionX,
1660 (int) mParentDownMotionY)) {
1661 onDropToDelete();
1662 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001663 } else {
Jim Millerafef5b22013-10-13 16:49:43 -07001664 if (DEBUG_WARP) Log.v(TAG, "calling onUnhandledTap()");
1665 if (mWarpPageExposed && !isAnimatingWarpPage()) {
1666 animateWarpPageOffScreen("unhandled tap", true);
1667 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001668 onUnhandledTap(ev);
1669 }
Jim Millerd6523da2012-10-21 16:47:02 -07001670
1671 // Remove the callback to wait for the side page hover timeout
1672 removeCallbacks(mSidePageHoverRunnable);
1673 // End any intermediate reordering states
1674 resetTouchState();
Jim Millerdcb3d842012-08-23 19:18:12 -07001675 break;
1676
1677 case MotionEvent.ACTION_CANCEL:
1678 if (mTouchState == TOUCH_STATE_SCROLLING) {
1679 snapToDestination();
1680 }
Jim Millerd6523da2012-10-21 16:47:02 -07001681 resetTouchState();
Jim Millerdcb3d842012-08-23 19:18:12 -07001682 break;
1683
1684 case MotionEvent.ACTION_POINTER_UP:
1685 onSecondaryPointerUp(ev);
1686 break;
1687 }
1688
1689 return true;
1690 }
1691
Michael Jurka1254f2f2012-10-25 11:44:31 -07001692 //public abstract void onFlingToDelete(View v);
Michael Jurka75b5cfb2012-11-15 18:22:47 -08001693 public abstract void onRemoveView(View v, boolean deletePermanently);
Winson Chung4752e7d2012-11-20 17:06:04 -08001694 public abstract void onRemoveViewAnimationCompleted();
Michael Jurka1254f2f2012-10-25 11:44:31 -07001695 public abstract void onAddView(View v, int index);
1696
Jim Millerd6523da2012-10-21 16:47:02 -07001697 private void resetTouchState() {
1698 releaseVelocityTracker();
1699 endReordering();
Jim Millercaf24fc2013-09-10 18:37:01 -07001700 setTouchState(TOUCH_STATE_REST);
Jim Millerd6523da2012-10-21 16:47:02 -07001701 mActivePointerId = INVALID_POINTER;
1702 mDownEventOnEdge = false;
1703 }
1704
1705 protected void onUnhandledTap(MotionEvent ev) {}
1706
Jim Millerdcb3d842012-08-23 19:18:12 -07001707 @Override
1708 public boolean onGenericMotionEvent(MotionEvent event) {
1709 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1710 switch (event.getAction()) {
1711 case MotionEvent.ACTION_SCROLL: {
1712 // Handle mouse (or ext. device) by shifting the page depending on the scroll
1713 final float vscroll;
1714 final float hscroll;
1715 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1716 vscroll = 0;
1717 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1718 } else {
1719 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1720 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1721 }
1722 if (hscroll != 0 || vscroll != 0) {
1723 if (hscroll > 0 || vscroll > 0) {
1724 scrollRight();
1725 } else {
1726 scrollLeft();
1727 }
1728 return true;
1729 }
1730 }
1731 }
1732 }
1733 return super.onGenericMotionEvent(event);
1734 }
1735
1736 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1737 if (mVelocityTracker == null) {
1738 mVelocityTracker = VelocityTracker.obtain();
1739 }
1740 mVelocityTracker.addMovement(ev);
1741 }
1742
1743 private void releaseVelocityTracker() {
1744 if (mVelocityTracker != null) {
1745 mVelocityTracker.recycle();
1746 mVelocityTracker = null;
1747 }
1748 }
1749
1750 private void onSecondaryPointerUp(MotionEvent ev) {
1751 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1752 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1753 final int pointerId = ev.getPointerId(pointerIndex);
1754 if (pointerId == mActivePointerId) {
1755 // This was our active pointer going up. Choose a new
1756 // active pointer and adjust accordingly.
1757 // TODO: Make this decision more intelligent.
1758 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1759 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1760 mLastMotionY = ev.getY(newPointerIndex);
1761 mLastMotionXRemainder = 0;
1762 mActivePointerId = ev.getPointerId(newPointerIndex);
1763 if (mVelocityTracker != null) {
1764 mVelocityTracker.clear();
1765 }
1766 }
1767 }
1768
Jim Millerdcb3d842012-08-23 19:18:12 -07001769 @Override
1770 public void requestChildFocus(View child, View focused) {
1771 super.requestChildFocus(child, focused);
1772 int page = indexToPage(indexOfChild(child));
1773 if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
1774 snapToPage(page);
1775 }
1776 }
1777
1778 protected int getChildIndexForRelativeOffset(int relativeOffset) {
1779 final int childCount = getChildCount();
1780 int left;
1781 int right;
1782 for (int i = 0; i < childCount; ++i) {
1783 left = getRelativeChildOffset(i);
1784 right = (left + getScaledMeasuredWidth(getPageAt(i)));
1785 if (left <= relativeOffset && relativeOffset <= right) {
1786 return i;
1787 }
1788 }
1789 return -1;
1790 }
1791
1792 protected int getChildWidth(int index) {
1793 // This functions are called enough times that it actually makes a difference in the
1794 // profiler -- so just inline the max() here
1795 final int measuredWidth = getPageAt(index).getMeasuredWidth();
1796 final int minWidth = mMinimumWidth;
1797 return (minWidth > measuredWidth) ? minWidth : measuredWidth;
1798 }
Jim Millerb5f3b702012-10-21 19:09:23 -07001799
Jim Millerd6523da2012-10-21 16:47:02 -07001800 int getPageNearestToPoint(float x) {
1801 int index = 0;
1802 for (int i = 0; i < getChildCount(); ++i) {
1803 if (x < getChildAt(i).getRight() - getScrollX()) {
1804 return index;
1805 } else {
1806 index++;
1807 }
1808 }
Jim Millerb5f3b702012-10-21 19:09:23 -07001809 return Math.min(index, getChildCount() - 1);
Jim Millerd6523da2012-10-21 16:47:02 -07001810 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001811
1812 int getPageNearestToCenterOfScreen() {
1813 int minDistanceFromScreenCenter = Integer.MAX_VALUE;
1814 int minDistanceFromScreenCenterIndex = -1;
Winson Chungefc49252012-10-26 15:41:27 -07001815 int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
Jim Millerdcb3d842012-08-23 19:18:12 -07001816 final int childCount = getChildCount();
1817 for (int i = 0; i < childCount; ++i) {
1818 View layout = (View) getPageAt(i);
1819 int childWidth = getScaledMeasuredWidth(layout);
1820 int halfChildWidth = (childWidth / 2);
Winson Chungefc49252012-10-26 15:41:27 -07001821 int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
Jim Millerdcb3d842012-08-23 19:18:12 -07001822 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
1823 if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
1824 minDistanceFromScreenCenter = distanceFromScreenCenter;
1825 minDistanceFromScreenCenterIndex = i;
1826 }
1827 }
1828 return minDistanceFromScreenCenterIndex;
1829 }
1830
1831 protected void snapToDestination() {
Jim Miller1c058a62013-10-13 19:35:47 -07001832 final int newPage = getPageNearestToCenterOfScreen();
Jim Millerc162dd02013-09-25 18:57:43 -07001833 if (isWarping()) {
Jim Miller1c058a62013-10-13 19:35:47 -07001834 cancelWarpAnimation("snapToDestination", mCurrentPage != newPage);
Jim Millerc162dd02013-09-25 18:57:43 -07001835 }
Jim Miller1c058a62013-10-13 19:35:47 -07001836 snapToPage(newPage, getPageSnapDuration());
Jim Millerc162dd02013-09-25 18:57:43 -07001837 }
1838
1839 private int getPageSnapDuration() {
1840 return isWarping() ? WARP_SNAP_DURATION : PAGE_SNAP_ANIMATION_DURATION;
Jim Millerdcb3d842012-08-23 19:18:12 -07001841 }
1842
1843 private static class ScrollInterpolator implements Interpolator {
1844 public ScrollInterpolator() {
1845 }
1846
1847 public float getInterpolation(float t) {
1848 t -= 1.0f;
1849 return t*t*t*t*t + 1;
1850 }
1851 }
1852
1853 // We want the duration of the page snap animation to be influenced by the distance that
1854 // the screen has to travel, however, we don't want this duration to be effected in a
1855 // purely linear fashion. Instead, we use this method to moderate the effect that the distance
1856 // of travel has on the overall snap duration.
1857 float distanceInfluenceForSnapDuration(float f) {
1858 f -= 0.5f; // center the values about 0.
1859 f *= 0.3f * Math.PI / 2.0f;
1860 return (float) Math.sin(f);
1861 }
1862
1863 protected void snapToPageWithVelocity(int whichPage, int velocity) {
1864 whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1));
Winson Chungefc49252012-10-26 15:41:27 -07001865 int halfScreenSize = getViewportWidth() / 2;
Jim Millerdcb3d842012-08-23 19:18:12 -07001866
Jim Millerc162dd02013-09-25 18:57:43 -07001867 if (isWarping()) {
Jim Miller1c058a62013-10-13 19:35:47 -07001868 cancelWarpAnimation("snapToPageWithVelocity", mCurrentPage != whichPage);
Jim Millerc162dd02013-09-25 18:57:43 -07001869 }
1870
Jim Millerdcb3d842012-08-23 19:18:12 -07001871 if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
1872 if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): "
Winson Chungefc49252012-10-26 15:41:27 -07001873 + getViewportWidth() + ", " + getChildWidth(whichPage));
Jim Millerdcb3d842012-08-23 19:18:12 -07001874 final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1875 int delta = newX - mUnboundedScrollX;
1876 int duration = 0;
1877
1878 if (Math.abs(velocity) < mMinFlingVelocity) {
1879 // If the velocity is low enough, then treat this more as an automatic page advance
1880 // as opposed to an apparent physical response to flinging
Jim Millerc162dd02013-09-25 18:57:43 -07001881 snapToPage(whichPage, getPageSnapDuration());
Jim Millerdcb3d842012-08-23 19:18:12 -07001882 return;
1883 }
1884
1885 // Here we compute a "distance" that will be used in the computation of the overall
1886 // snap duration. This is a function of the actual distance that needs to be traveled;
1887 // we keep this value close to half screen size in order to reduce the variance in snap
1888 // duration as a function of the distance the page needs to travel.
1889 float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
1890 float distance = halfScreenSize + halfScreenSize *
1891 distanceInfluenceForSnapDuration(distanceRatio);
1892
1893 velocity = Math.abs(velocity);
1894 velocity = Math.max(mMinSnapVelocity, velocity);
1895
1896 // we want the page's snap velocity to approximately match the velocity at which the
1897 // user flings, so we scale the duration by a value near to the derivative of the scroll
1898 // interpolator at zero, ie. 5. We use 4 to make it a little slower.
1899 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
1900
1901 snapToPage(whichPage, delta, duration);
1902 }
1903
1904 protected void snapToPage(int whichPage) {
Jim Millerc162dd02013-09-25 18:57:43 -07001905 snapToPage(whichPage, getPageSnapDuration());
Jim Millerdcb3d842012-08-23 19:18:12 -07001906 }
Jim Miller19a52672012-10-23 19:52:04 -07001907 protected void snapToPageImmediately(int whichPage) {
Jim Millerc162dd02013-09-25 18:57:43 -07001908 snapToPage(whichPage, getPageSnapDuration(), true);
Jim Miller19a52672012-10-23 19:52:04 -07001909 }
Jim Millerdcb3d842012-08-23 19:18:12 -07001910
1911 protected void snapToPage(int whichPage, int duration) {
Jim Miller19a52672012-10-23 19:52:04 -07001912 snapToPage(whichPage, duration, false);
1913 }
1914 protected void snapToPage(int whichPage, int duration, boolean immediate) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001915 whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1));
1916
1917 if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage));
Winson Chungefc49252012-10-26 15:41:27 -07001918 if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getViewportWidth() + ", "
Jim Millerdcb3d842012-08-23 19:18:12 -07001919 + getChildWidth(whichPage));
1920 int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage);
1921 final int sX = mUnboundedScrollX;
1922 final int delta = newX - sX;
Jim Miller19a52672012-10-23 19:52:04 -07001923 snapToPage(whichPage, delta, duration, immediate);
Jim Millerdcb3d842012-08-23 19:18:12 -07001924 }
1925
1926 protected void snapToPage(int whichPage, int delta, int duration) {
Jim Miller19a52672012-10-23 19:52:04 -07001927 snapToPage(whichPage, delta, duration, false);
1928 }
Jim Millercaf24fc2013-09-10 18:37:01 -07001929
Jim Miller19a52672012-10-23 19:52:04 -07001930 protected void snapToPage(int whichPage, int delta, int duration, boolean immediate) {
Selim Cineked88cee2014-01-10 17:28:15 -08001931 if (isWarping() && whichPage == mCurrentPage+1) {
1932 mNextPage = getPageWarpIndex(); // jump to the warp page
Jim Millercaf24fc2013-09-10 18:37:01 -07001933 if (DEBUG_WARP) Log.v(TAG, "snapToPage(" + whichPage + ") : reset mPageSwapIndex");
Jim Millercaf24fc2013-09-10 18:37:01 -07001934 } else {
1935 mNextPage = whichPage;
1936 }
1937
Selim Cineked88cee2014-01-10 17:28:15 -08001938 if(mWarpPageExposed) {
John Spurlock56d007b2013-10-28 18:40:56 -04001939 dispatchOnPageEndWarp();
Selim Cineked88cee2014-01-10 17:28:15 -08001940 mWarpPageExposed = false;
Jim Millerf4db8f92013-09-20 14:21:50 -07001941 }
Selim Cineked88cee2014-01-10 17:28:15 -08001942 notifyPageSwitching(whichPage);
1943
Jim Millerf4db8f92013-09-20 14:21:50 -07001944
Jim Millerdcb3d842012-08-23 19:18:12 -07001945 View focusedChild = getFocusedChild();
Svetoslav Ganov45942ca2012-10-31 19:46:24 -07001946 if (focusedChild != null && whichPage != mCurrentPage &&
1947 focusedChild == getPageAt(mCurrentPage)) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001948 focusedChild.clearFocus();
1949 }
1950
1951 pageBeginMoving();
1952 awakenScrollBars(duration);
Jim Miller19a52672012-10-23 19:52:04 -07001953 if (immediate) {
1954 duration = 0;
1955 } else if (duration == 0) {
Jim Millerdcb3d842012-08-23 19:18:12 -07001956 duration = Math.abs(delta);
1957 }
1958
1959 if (!mScroller.isFinished()) mScroller.abortAnimation();
1960 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
1961
John Spurlockbb5c9412012-10-31 09:46:15 -04001962 notifyPageSwitched();
Jim Miller19a52672012-10-23 19:52:04 -07001963
1964 // Trigger a compute() to finish switching pages if necessary
1965 if (immediate) {
1966 computeScroll();
1967 }
1968
Winson Chungf3b9ec82012-11-01 14:48:51 -07001969 mForceScreenScrolled = true;
Jim Millerdcb3d842012-08-23 19:18:12 -07001970 invalidate();
1971 }
1972
Jim Millerc162dd02013-09-25 18:57:43 -07001973 protected boolean isWarping() {
Selim Cineked88cee2014-01-10 17:28:15 -08001974 return mWarpPageExposed;
Jim Millerc162dd02013-09-25 18:57:43 -07001975 }
1976
Jim Millerdcb3d842012-08-23 19:18:12 -07001977 public void scrollLeft() {
1978 if (mScroller.isFinished()) {
1979 if (mCurrentPage > 0) snapToPage(mCurrentPage - 1);
1980 } else {
1981 if (mNextPage > 0) snapToPage(mNextPage - 1);
1982 }
1983 }
1984
1985 public void scrollRight() {
1986 if (mScroller.isFinished()) {
1987 if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1);
1988 } else {
1989 if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1);
1990 }
1991 }
1992
1993 public int getPageForView(View v) {
1994 int result = -1;
1995 if (v != null) {
1996 ViewParent vp = v.getParent();
1997 int count = getChildCount();
1998 for (int i = 0; i < count; i++) {
1999 if (vp == getPageAt(i)) {
2000 return i;
2001 }
2002 }
2003 }
2004 return result;
2005 }
2006
Jim Millerdcb3d842012-08-23 19:18:12 -07002007 public static class SavedState extends BaseSavedState {
2008 int currentPage = -1;
2009
2010 SavedState(Parcelable superState) {
2011 super(superState);
2012 }
2013
2014 private SavedState(Parcel in) {
2015 super(in);
2016 currentPage = in.readInt();
2017 }
2018
2019 @Override
2020 public void writeToParcel(Parcel out, int flags) {
2021 super.writeToParcel(out, flags);
2022 out.writeInt(currentPage);
2023 }
2024
2025 public static final Parcelable.Creator<SavedState> CREATOR =
2026 new Parcelable.Creator<SavedState>() {
2027 public SavedState createFromParcel(Parcel in) {
2028 return new SavedState(in);
2029 }
2030
2031 public SavedState[] newArray(int size) {
2032 return new SavedState[size];
2033 }
2034 };
2035 }
2036
2037 protected View getScrollingIndicator() {
2038 return null;
2039 }
2040
2041 protected boolean isScrollingIndicatorEnabled() {
2042 return false;
2043 }
2044
2045 Runnable hideScrollingIndicatorRunnable = new Runnable() {
2046 @Override
2047 public void run() {
2048 hideScrollingIndicator(false);
2049 }
2050 };
2051
2052 protected void flashScrollingIndicator(boolean animated) {
2053 removeCallbacks(hideScrollingIndicatorRunnable);
2054 showScrollingIndicator(!animated);
2055 postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration);
2056 }
2057
2058 protected void showScrollingIndicator(boolean immediately) {
2059 mShouldShowScrollIndicator = true;
2060 mShouldShowScrollIndicatorImmediately = true;
2061 if (getChildCount() <= 1) return;
2062 if (!isScrollingIndicatorEnabled()) return;
2063
2064 mShouldShowScrollIndicator = false;
2065 getScrollingIndicator();
2066 if (mScrollIndicator != null) {
2067 // Fade the indicator in
2068 updateScrollingIndicatorPosition();
2069 mScrollIndicator.setVisibility(View.VISIBLE);
2070 cancelScrollingIndicatorAnimations();
2071 if (immediately) {
2072 mScrollIndicator.setAlpha(1f);
2073 } else {
2074 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f);
2075 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration);
2076 mScrollIndicatorAnimator.start();
2077 }
2078 }
2079 }
2080
2081 protected void cancelScrollingIndicatorAnimations() {
2082 if (mScrollIndicatorAnimator != null) {
2083 mScrollIndicatorAnimator.cancel();
2084 }
2085 }
2086
2087 protected void hideScrollingIndicator(boolean immediately) {
2088 if (getChildCount() <= 1) return;
2089 if (!isScrollingIndicatorEnabled()) return;
2090
2091 getScrollingIndicator();
2092 if (mScrollIndicator != null) {
2093 // Fade the indicator out
2094 updateScrollingIndicatorPosition();
2095 cancelScrollingIndicatorAnimations();
2096 if (immediately) {
2097 mScrollIndicator.setVisibility(View.INVISIBLE);
2098 mScrollIndicator.setAlpha(0f);
2099 } else {
2100 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f);
2101 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration);
2102 mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() {
2103 private boolean cancelled = false;
2104 @Override
2105 public void onAnimationCancel(android.animation.Animator animation) {
2106 cancelled = true;
2107 }
2108 @Override
2109 public void onAnimationEnd(Animator animation) {
2110 if (!cancelled) {
2111 mScrollIndicator.setVisibility(View.INVISIBLE);
2112 }
2113 }
2114 });
2115 mScrollIndicatorAnimator.start();
2116 }
2117 }
2118 }
2119
2120 /**
2121 * To be overridden by subclasses to determine whether the scroll indicator should stretch to
2122 * fill its space on the track or not.
2123 */
2124 protected boolean hasElasticScrollIndicator() {
2125 return true;
2126 }
2127
2128 private void updateScrollingIndicator() {
2129 if (getChildCount() <= 1) return;
2130 if (!isScrollingIndicatorEnabled()) return;
2131
2132 getScrollingIndicator();
2133 if (mScrollIndicator != null) {
2134 updateScrollingIndicatorPosition();
2135 }
2136 if (mShouldShowScrollIndicator) {
2137 showScrollingIndicator(mShouldShowScrollIndicatorImmediately);
2138 }
2139 }
2140
2141 private void updateScrollingIndicatorPosition() {
2142 if (!isScrollingIndicatorEnabled()) return;
2143 if (mScrollIndicator == null) return;
2144 int numPages = getChildCount();
Winson Chungefc49252012-10-26 15:41:27 -07002145 int pageWidth = getViewportWidth();
Jim Millerdcb3d842012-08-23 19:18:12 -07002146 int lastChildIndex = Math.max(0, getChildCount() - 1);
2147 int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex);
2148 int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight;
2149 int indicatorWidth = mScrollIndicator.getMeasuredWidth() -
2150 mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight();
2151
2152 float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX));
2153 int indicatorSpace = trackWidth / numPages;
2154 int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft;
2155 if (hasElasticScrollIndicator()) {
2156 if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) {
2157 mScrollIndicator.getLayoutParams().width = indicatorSpace;
2158 mScrollIndicator.requestLayout();
2159 }
2160 } else {
2161 int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2;
2162 indicatorPos += indicatorCenterOffset;
2163 }
2164 mScrollIndicator.setTranslationX(indicatorPos);
2165 }
2166
Jim Millerd6523da2012-10-21 16:47:02 -07002167 // Animate the drag view back to the original position
Winson Chung9dc99232012-10-29 17:43:18 -07002168 void animateDragViewToOriginalPosition() {
Jim Millerd6523da2012-10-21 16:47:02 -07002169 if (mDragView != null) {
2170 AnimatorSet anim = new AnimatorSet();
2171 anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
2172 anim.playTogether(
2173 ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
2174 ObjectAnimator.ofFloat(mDragView, "translationY", 0f));
Winson Chung9dc99232012-10-29 17:43:18 -07002175 anim.addListener(new AnimatorListenerAdapter() {
2176 @Override
2177 public void onAnimationEnd(Animator animation) {
2178 onPostReorderingAnimationCompleted();
2179 }
2180 });
Jim Millerd6523da2012-10-21 16:47:02 -07002181 anim.start();
2182 }
2183 }
2184
2185 // "Zooms out" the PagedView to reveal more side pages
Adam Cohen70009e42012-10-30 16:48:22 -07002186 protected boolean zoomOut() {
Jim Miller19a52672012-10-23 19:52:04 -07002187 if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2188 mZoomInOutAnim.cancel();
2189 }
Jim Millerd6523da2012-10-21 16:47:02 -07002190
Jim Miller19a52672012-10-23 19:52:04 -07002191 if (!(getScaleX() < 1f || getScaleY() < 1f)) {
Jim Millerd6523da2012-10-21 16:47:02 -07002192 mZoomInOutAnim = new AnimatorSet();
2193 mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2194 mZoomInOutAnim.playTogether(
2195 ObjectAnimator.ofFloat(this, "scaleX", mMinScale),
2196 ObjectAnimator.ofFloat(this, "scaleY", mMinScale));
Winson Chungf3b9ec82012-11-01 14:48:51 -07002197 mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2198 @Override
2199 public void onAnimationStart(Animator animation) {
2200 // Show the delete drop target
2201 if (mDeleteDropTarget != null) {
2202 mDeleteDropTarget.setVisibility(View.VISIBLE);
2203 mDeleteDropTarget.animate().alpha(1f)
2204 .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2205 .setListener(new AnimatorListenerAdapter() {
2206 @Override
2207 public void onAnimationStart(Animator animation) {
2208 mDeleteDropTarget.setAlpha(0f);
2209 }
2210 });
2211 }
2212 }
2213 });
Jim Millerd6523da2012-10-21 16:47:02 -07002214 mZoomInOutAnim.start();
2215 return true;
2216 }
2217 return false;
2218 }
2219
2220 protected void onStartReordering() {
Svetoslav Ganovc4842c12012-10-31 14:33:32 -07002221 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
2222 announceForAccessibility(mContext.getString(
2223 R.string.keyguard_accessibility_widget_reorder_start));
2224 }
2225
Jim Miller19a52672012-10-23 19:52:04 -07002226 // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
Jim Millercaf24fc2013-09-10 18:37:01 -07002227 setTouchState(TOUCH_STATE_REORDERING);
Jim Miller19a52672012-10-23 19:52:04 -07002228 mIsReordering = true;
2229
Winson Chungf3b9ec82012-11-01 14:48:51 -07002230 // Mark all the non-widget pages as invisible
2231 getVisiblePages(mTempVisiblePagesRange);
2232 boundByReorderablePages(true, mTempVisiblePagesRange);
2233 for (int i = 0; i < getPageCount(); ++i) {
2234 if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2235 getPageAt(i).setAlpha(0f);
2236 }
2237 }
2238
Jim Miller19a52672012-10-23 19:52:04 -07002239 // We must invalidate to trigger a redraw to update the layers such that the drag view
2240 // is always drawn on top
2241 invalidate();
Jim Millerd6523da2012-10-21 16:47:02 -07002242 }
2243
Winson Chung9dc99232012-10-29 17:43:18 -07002244 private void onPostReorderingAnimationCompleted() {
2245 // Trigger the callback when reordering has settled
2246 --mPostReorderingPreZoomInRemainingAnimationCount;
2247 if (mPostReorderingPreZoomInRunnable != null &&
2248 mPostReorderingPreZoomInRemainingAnimationCount == 0) {
2249 mPostReorderingPreZoomInRunnable.run();
2250 mPostReorderingPreZoomInRunnable = null;
2251 }
2252 }
2253
Jim Millerd6523da2012-10-21 16:47:02 -07002254 protected void onEndReordering() {
Svetoslav Ganovc4842c12012-10-31 14:33:32 -07002255 if (AccessibilityManager.getInstance(mContext).isEnabled()) {
Jim Miller1962e262013-09-25 17:08:48 -07002256 if (mDeleteString != null) {
2257 announceForAccessibility(mDeleteString);
2258 mDeleteString = null;
2259 } else {
2260 announceForAccessibility(mContext.getString(
2261 R.string.keyguard_accessibility_widget_reorder_end));
2262 }
Svetoslav Ganovc4842c12012-10-31 14:33:32 -07002263 }
Jim Miller19a52672012-10-23 19:52:04 -07002264 mIsReordering = false;
Winson Chungf3b9ec82012-11-01 14:48:51 -07002265
2266 // Mark all the non-widget pages as visible again
2267 getVisiblePages(mTempVisiblePagesRange);
2268 boundByReorderablePages(true, mTempVisiblePagesRange);
2269 for (int i = 0; i < getPageCount(); ++i) {
2270 if (i < mTempVisiblePagesRange[0] || i > mTempVisiblePagesRange[1]) {
2271 getPageAt(i).setAlpha(1f);
2272 }
2273 }
Jim Millerd6523da2012-10-21 16:47:02 -07002274 }
Jim Millerb5f3b702012-10-21 19:09:23 -07002275
Jim Miller19a52672012-10-23 19:52:04 -07002276 public boolean startReordering() {
2277 int dragViewIndex = getPageNearestToCenterOfScreen();
2278 mTempVisiblePagesRange[0] = 0;
2279 mTempVisiblePagesRange[1] = getPageCount() - 1;
2280 boundByReorderablePages(true, mTempVisiblePagesRange);
Jim Millerd6523da2012-10-21 16:47:02 -07002281
Jim Miller19a52672012-10-23 19:52:04 -07002282 // Check if we are within the reordering range
2283 if (mTempVisiblePagesRange[0] <= dragViewIndex &&
2284 dragViewIndex <= mTempVisiblePagesRange[1]) {
Adam Cohen3f1c3c72013-10-31 18:40:59 -07002285 mReorderingStarted = true;
Jim Miller19a52672012-10-23 19:52:04 -07002286 if (zoomOut()) {
2287 // Find the drag view under the pointer
2288 mDragView = getChildAt(dragViewIndex);
Jim Millerd6523da2012-10-21 16:47:02 -07002289
Jim Miller19a52672012-10-23 19:52:04 -07002290 onStartReordering();
2291 }
2292 return true;
Jim Millerd6523da2012-10-21 16:47:02 -07002293 }
Jim Miller19a52672012-10-23 19:52:04 -07002294 return false;
Jim Millerd6523da2012-10-21 16:47:02 -07002295 }
2296
Jim Miller19a52672012-10-23 19:52:04 -07002297 boolean isReordering(boolean testTouchState) {
2298 boolean state = mIsReordering;
2299 if (testTouchState) {
2300 state &= (mTouchState == TOUCH_STATE_REORDERING);
2301 }
2302 return state;
2303 }
Jim Millerd6523da2012-10-21 16:47:02 -07002304 void endReordering() {
Jim Miller19a52672012-10-23 19:52:04 -07002305 // For simplicity, we call endReordering sometimes even if reordering was never started.
2306 // In that case, we don't want to do anything.
2307 if (!mReorderingStarted) return;
2308 mReorderingStarted = false;
Jim Miller19a52672012-10-23 19:52:04 -07002309
Jim Millerd6523da2012-10-21 16:47:02 -07002310 // If we haven't flung-to-delete the current child, then we just animate the drag view
2311 // back into position
Winson Chung70aa5282012-10-30 10:56:37 -07002312 final Runnable onCompleteRunnable = new Runnable() {
2313 @Override
2314 public void run() {
2315 onEndReordering();
2316 }
2317 };
Winson Chungf3b9ec82012-11-01 14:48:51 -07002318 if (!mDeferringForDelete) {
Winson Chung9dc99232012-10-29 17:43:18 -07002319 mPostReorderingPreZoomInRunnable = new Runnable() {
2320 public void run() {
Winson Chung9dc99232012-10-29 17:43:18 -07002321 zoomIn(onCompleteRunnable);
2322 };
2323 };
Jim Miller19a52672012-10-23 19:52:04 -07002324
Winson Chung9dc99232012-10-29 17:43:18 -07002325 mPostReorderingPreZoomInRemainingAnimationCount =
2326 NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
2327 // Snap to the current page
2328 snapToPage(indexOfChild(mDragView), 0);
2329 // Animate the drag view back to the front position
2330 animateDragViewToOriginalPosition();
Winson Chung70aa5282012-10-30 10:56:37 -07002331 } else {
Winson Chungf3b9ec82012-11-01 14:48:51 -07002332 // Handled in post-delete-animation-callbacks
Jim Millerd6523da2012-10-21 16:47:02 -07002333 }
2334 }
2335
Jim Millerd6523da2012-10-21 16:47:02 -07002336 // "Zooms in" the PagedView to highlight the current page
Adam Cohen70009e42012-10-30 16:48:22 -07002337 protected boolean zoomIn(final Runnable onCompleteRunnable) {
Jim Miller19a52672012-10-23 19:52:04 -07002338 if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) {
2339 mZoomInOutAnim.cancel();
2340 }
Jim Millerd6523da2012-10-21 16:47:02 -07002341 if (getScaleX() < 1f || getScaleY() < 1f) {
Jim Millerd6523da2012-10-21 16:47:02 -07002342 mZoomInOutAnim = new AnimatorSet();
2343 mZoomInOutAnim.setDuration(REORDERING_ZOOM_IN_OUT_DURATION);
2344 mZoomInOutAnim.playTogether(
2345 ObjectAnimator.ofFloat(this, "scaleX", 1f),
2346 ObjectAnimator.ofFloat(this, "scaleY", 1f));
2347 mZoomInOutAnim.addListener(new AnimatorListenerAdapter() {
2348 @Override
Winson Chungf3b9ec82012-11-01 14:48:51 -07002349 public void onAnimationStart(Animator animation) {
2350 // Hide the delete drop target
2351 if (mDeleteDropTarget != null) {
2352 mDeleteDropTarget.animate().alpha(0f)
2353 .setDuration(REORDERING_DELETE_DROP_TARGET_FADE_DURATION)
2354 .setListener(new AnimatorListenerAdapter() {
2355 @Override
2356 public void onAnimationEnd(Animator animation) {
2357 mDeleteDropTarget.setVisibility(View.GONE);
2358 }
2359 });
2360 }
2361 }
2362 @Override
Jim Millerd6523da2012-10-21 16:47:02 -07002363 public void onAnimationCancel(Animator animation) {
2364 mDragView = null;
2365 }
2366 @Override
2367 public void onAnimationEnd(Animator animation) {
2368 mDragView = null;
2369 if (onCompleteRunnable != null) {
2370 onCompleteRunnable.run();
2371 }
2372 }
2373 });
2374 mZoomInOutAnim.start();
2375 return true;
Jim Miller19a52672012-10-23 19:52:04 -07002376 } else {
2377 if (onCompleteRunnable != null) {
2378 onCompleteRunnable.run();
2379 }
Jim Millerd6523da2012-10-21 16:47:02 -07002380 }
2381 return false;
2382 }
2383
2384 /*
Jim Millerb5f3b702012-10-21 19:09:23 -07002385 * Flinging to delete - IN PROGRESS
Jim Millerd6523da2012-10-21 16:47:02 -07002386 */
2387 private PointF isFlingingToDelete() {
2388 ViewConfiguration config = ViewConfiguration.get(getContext());
2389 mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity());
2390
2391 if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) {
2392 // Do a quick dot product test to ensure that we are flinging upwards
2393 PointF vel = new PointF(mVelocityTracker.getXVelocity(),
2394 mVelocityTracker.getYVelocity());
2395 PointF upVec = new PointF(0f, -1f);
2396 float theta = (float) Math.acos(((vel.x * upVec.x) + (vel.y * upVec.y)) /
2397 (vel.length() * upVec.length()));
2398 if (theta <= Math.toRadians(FLING_TO_DELETE_MAX_FLING_DEGREES)) {
2399 return vel;
2400 }
2401 }
2402 return null;
2403 }
2404
2405 /**
2406 * Creates an animation from the current drag view along its current velocity vector.
2407 * For this animation, the alpha runs for a fixed duration and we update the position
2408 * progressively.
2409 */
2410 private static class FlingAlongVectorAnimatorUpdateListener implements AnimatorUpdateListener {
2411 private View mDragView;
2412 private PointF mVelocity;
2413 private Rect mFrom;
2414 private long mPrevTime;
2415 private float mFriction;
2416
2417 private final TimeInterpolator mAlphaInterpolator = new DecelerateInterpolator(0.75f);
2418
2419 public FlingAlongVectorAnimatorUpdateListener(View dragView, PointF vel, Rect from,
2420 long startTime, float friction) {
2421 mDragView = dragView;
2422 mVelocity = vel;
2423 mFrom = from;
2424 mPrevTime = startTime;
2425 mFriction = 1f - (mDragView.getResources().getDisplayMetrics().density * friction);
2426 }
2427
2428 @Override
2429 public void onAnimationUpdate(ValueAnimator animation) {
2430 float t = ((Float) animation.getAnimatedValue()).floatValue();
2431 long curTime = AnimationUtils.currentAnimationTimeMillis();
2432
2433 mFrom.left += (mVelocity.x * (curTime - mPrevTime) / 1000f);
2434 mFrom.top += (mVelocity.y * (curTime - mPrevTime) / 1000f);
2435
2436 mDragView.setTranslationX(mFrom.left);
2437 mDragView.setTranslationY(mFrom.top);
2438 mDragView.setAlpha(1f - mAlphaInterpolator.getInterpolation(t));
2439
2440 mVelocity.x *= mFriction;
2441 mVelocity.y *= mFriction;
2442 mPrevTime = curTime;
2443 }
2444 };
2445
Winson Chungf3b9ec82012-11-01 14:48:51 -07002446 private Runnable createPostDeleteAnimationRunnable(final View dragView) {
2447 return new Runnable() {
2448 @Override
2449 public void run() {
2450 int dragViewIndex = indexOfChild(dragView);
2451
2452 // For each of the pages around the drag view, animate them from the previous
2453 // position to the new position in the layout (as a result of the drag view moving
2454 // in the layout)
2455 // NOTE: We can make an assumption here because we have side-bound pages that we
2456 // will always have pages to animate in from the left
2457 getVisiblePages(mTempVisiblePagesRange);
2458 boundByReorderablePages(true, mTempVisiblePagesRange);
2459 boolean isLastWidgetPage = (mTempVisiblePagesRange[0] == mTempVisiblePagesRange[1]);
2460 boolean slideFromLeft = (isLastWidgetPage ||
2461 dragViewIndex > mTempVisiblePagesRange[0]);
2462
2463 // Setup the scroll to the correct page before we swap the views
2464 if (slideFromLeft) {
2465 snapToPageImmediately(dragViewIndex - 1);
2466 }
2467
2468 int firstIndex = (isLastWidgetPage ? 0 : mTempVisiblePagesRange[0]);
2469 int lastIndex = Math.min(mTempVisiblePagesRange[1], getPageCount() - 1);
2470 int lowerIndex = (slideFromLeft ? firstIndex : dragViewIndex + 1 );
2471 int upperIndex = (slideFromLeft ? dragViewIndex - 1 : lastIndex);
2472 ArrayList<Animator> animations = new ArrayList<Animator>();
2473 for (int i = lowerIndex; i <= upperIndex; ++i) {
2474 View v = getChildAt(i);
2475 // dragViewIndex < pageUnderPointIndex, so after we remove the
2476 // drag view all subsequent views to pageUnderPointIndex will
2477 // shift down.
2478 int oldX = 0;
2479 int newX = 0;
2480 if (slideFromLeft) {
2481 if (i == 0) {
2482 // Simulate the page being offscreen with the page spacing
2483 oldX = getViewportOffsetX() + getChildOffset(i) - getChildWidth(i)
2484 - mPageSpacing;
2485 } else {
2486 oldX = getViewportOffsetX() + getChildOffset(i - 1);
2487 }
2488 newX = getViewportOffsetX() + getChildOffset(i);
2489 } else {
2490 oldX = getChildOffset(i) - getChildOffset(i - 1);
2491 newX = 0;
2492 }
2493
2494 // Animate the view translation from its old position to its new
2495 // position
2496 AnimatorSet anim = (AnimatorSet) v.getTag();
2497 if (anim != null) {
2498 anim.cancel();
2499 }
2500
2501 // Note: Hacky, but we want to skip any optimizations to not draw completely
2502 // hidden views
2503 v.setAlpha(Math.max(v.getAlpha(), 0.01f));
2504 v.setTranslationX(oldX - newX);
2505 anim = new AnimatorSet();
2506 anim.playTogether(
2507 ObjectAnimator.ofFloat(v, "translationX", 0f),
2508 ObjectAnimator.ofFloat(v, "alpha", 1f));
2509 animations.add(anim);
2510 v.setTag(anim);
2511 }
2512
2513 AnimatorSet slideAnimations = new AnimatorSet();
2514 slideAnimations.playTogether(animations);
2515 slideAnimations.setDuration(DELETE_SLIDE_IN_SIDE_PAGE_DURATION);
2516 slideAnimations.addListener(new AnimatorListenerAdapter() {
2517 @Override
2518 public void onAnimationEnd(Animator animation) {
2519 final Runnable onCompleteRunnable = new Runnable() {
2520 @Override
2521 public void run() {
2522 mDeferringForDelete = false;
2523 onEndReordering();
Winson Chung4752e7d2012-11-20 17:06:04 -08002524 onRemoveViewAnimationCompleted();
Winson Chungf3b9ec82012-11-01 14:48:51 -07002525 }
2526 };
2527 zoomIn(onCompleteRunnable);
2528 }
2529 });
2530 slideAnimations.start();
2531
2532 removeView(dragView);
Michael Jurka75b5cfb2012-11-15 18:22:47 -08002533 onRemoveView(dragView, true);
Winson Chungf3b9ec82012-11-01 14:48:51 -07002534 }
2535 };
2536 }
2537
Jim Millerd6523da2012-10-21 16:47:02 -07002538 public void onFlingToDelete(PointF vel) {
Jim Millerd6523da2012-10-21 16:47:02 -07002539 final long startTime = AnimationUtils.currentAnimationTimeMillis();
2540
2541 // NOTE: Because it takes time for the first frame of animation to actually be
2542 // called and we expect the animation to be a continuation of the fling, we have
2543 // to account for the time that has elapsed since the fling finished. And since
2544 // we don't have a startDelay, we will always get call to update when we call
2545 // start() (which we want to ignore).
2546 final TimeInterpolator tInterpolator = new TimeInterpolator() {
2547 private int mCount = -1;
2548 private long mStartTime;
2549 private float mOffset;
2550 /* Anonymous inner class ctor */ {
2551 mStartTime = startTime;
2552 }
2553
2554 @Override
2555 public float getInterpolation(float t) {
2556 if (mCount < 0) {
2557 mCount++;
2558 } else if (mCount == 0) {
2559 mOffset = Math.min(0.5f, (float) (AnimationUtils.currentAnimationTimeMillis() -
2560 mStartTime) / FLING_TO_DELETE_FADE_OUT_DURATION);
2561 mCount++;
2562 }
2563 return Math.min(1f, mOffset + t);
2564 }
2565 };
2566
2567 final Rect from = new Rect();
Jim Millerb5f3b702012-10-21 19:09:23 -07002568 final View dragView = mDragView;
Jim Millerd6523da2012-10-21 16:47:02 -07002569 from.left = (int) dragView.getTranslationX();
2570 from.top = (int) dragView.getTranslationY();
Jim Millerb5f3b702012-10-21 19:09:23 -07002571 AnimatorUpdateListener updateCb = new FlingAlongVectorAnimatorUpdateListener(dragView, vel,
2572 from, startTime, FLING_TO_DELETE_FRICTION);
Jim Millerd6523da2012-10-21 16:47:02 -07002573
Jim Miller1962e262013-09-25 17:08:48 -07002574 mDeleteString = getContext().getResources()
2575 .getString(R.string.keyguard_accessibility_widget_deleted,
2576 mDragView.getContentDescription());
Winson Chungf3b9ec82012-11-01 14:48:51 -07002577 final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
Jim Millerd6523da2012-10-21 16:47:02 -07002578
2579 // Create and start the animation
2580 ValueAnimator mDropAnim = new ValueAnimator();
2581 mDropAnim.setInterpolator(tInterpolator);
2582 mDropAnim.setDuration(FLING_TO_DELETE_FADE_OUT_DURATION);
2583 mDropAnim.setFloatValues(0f, 1f);
2584 mDropAnim.addUpdateListener(updateCb);
2585 mDropAnim.addListener(new AnimatorListenerAdapter() {
2586 public void onAnimationEnd(Animator animation) {
2587 onAnimationEndRunnable.run();
2588 }
2589 });
2590 mDropAnim.start();
Winson Chungf3b9ec82012-11-01 14:48:51 -07002591 mDeferringForDelete = true;
2592 }
2593
2594 /* Drag to delete */
2595 private boolean isHoveringOverDeleteDropTarget(int x, int y) {
2596 if (mDeleteDropTarget != null) {
Winson Chungc065a5d2012-11-07 17:17:33 -08002597 mAltTmpRect.set(0, 0, 0, 0);
2598 View parent = (View) mDeleteDropTarget.getParent();
2599 if (parent != null) {
2600 parent.getGlobalVisibleRect(mAltTmpRect);
2601 }
Winson Chungf3b9ec82012-11-01 14:48:51 -07002602 mDeleteDropTarget.getGlobalVisibleRect(mTmpRect);
Winson Chungc065a5d2012-11-07 17:17:33 -08002603 mTmpRect.offset(-mAltTmpRect.left, -mAltTmpRect.top);
Winson Chungf3b9ec82012-11-01 14:48:51 -07002604 return mTmpRect.contains(x, y);
2605 }
2606 return false;
2607 }
2608
2609 protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) {}
2610
2611 private void onDropToDelete() {
2612 final View dragView = mDragView;
2613
2614 final float toScale = 0f;
2615 final float toAlpha = 0f;
2616
2617 // Create and start the complex animation
2618 ArrayList<Animator> animations = new ArrayList<Animator>();
2619 AnimatorSet motionAnim = new AnimatorSet();
2620 motionAnim.setInterpolator(new DecelerateInterpolator(2));
2621 motionAnim.playTogether(
2622 ObjectAnimator.ofFloat(dragView, "scaleX", toScale),
2623 ObjectAnimator.ofFloat(dragView, "scaleY", toScale));
2624 animations.add(motionAnim);
2625
2626 AnimatorSet alphaAnim = new AnimatorSet();
2627 alphaAnim.setInterpolator(new LinearInterpolator());
2628 alphaAnim.playTogether(
2629 ObjectAnimator.ofFloat(dragView, "alpha", toAlpha));
2630 animations.add(alphaAnim);
2631
Jim Miller1962e262013-09-25 17:08:48 -07002632 mDeleteString = getContext().getResources()
2633 .getString(R.string.keyguard_accessibility_widget_deleted,
2634 mDragView.getContentDescription());
Winson Chungf3b9ec82012-11-01 14:48:51 -07002635 final Runnable onAnimationEndRunnable = createPostDeleteAnimationRunnable(dragView);
2636
2637 AnimatorSet anim = new AnimatorSet();
2638 anim.playTogether(animations);
2639 anim.setDuration(DRAG_TO_DELETE_FADE_OUT_DURATION);
2640 anim.addListener(new AnimatorListenerAdapter() {
2641 public void onAnimationEnd(Animator animation) {
2642 onAnimationEndRunnable.run();
2643 }
2644 });
2645 anim.start();
2646
2647 mDeferringForDelete = true;
Jim Millerd6523da2012-10-21 16:47:02 -07002648 }
2649
Jim Millerdcb3d842012-08-23 19:18:12 -07002650 /* Accessibility */
Jim Millerdcb3d842012-08-23 19:18:12 -07002651 @Override
2652 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2653 super.onInitializeAccessibilityNodeInfo(info);
2654 info.setScrollable(getPageCount() > 1);
2655 if (getCurrentPage() < getPageCount() - 1) {
2656 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2657 }
2658 if (getCurrentPage() > 0) {
2659 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2660 }
2661 }
2662
2663 @Override
2664 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2665 super.onInitializeAccessibilityEvent(event);
2666 event.setScrollable(true);
2667 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2668 event.setFromIndex(mCurrentPage);
2669 event.setToIndex(mCurrentPage);
2670 event.setItemCount(getChildCount());
2671 }
2672 }
2673
2674 @Override
2675 public boolean performAccessibilityAction(int action, Bundle arguments) {
2676 if (super.performAccessibilityAction(action, arguments)) {
2677 return true;
2678 }
2679 switch (action) {
2680 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2681 if (getCurrentPage() < getPageCount() - 1) {
2682 scrollRight();
2683 return true;
2684 }
2685 } break;
2686 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2687 if (getCurrentPage() > 0) {
2688 scrollLeft();
2689 return true;
2690 }
2691 } break;
2692 }
2693 return false;
2694 }
2695
2696 @Override
2697 public boolean onHoverEvent(android.view.MotionEvent event) {
2698 return true;
2699 }
Jim Millercaf24fc2013-09-10 18:37:01 -07002700
2701 void beginCameraEvent() {
2702 mIsCameraEvent = true;
2703 }
2704
2705 void endCameraEvent() {
2706 mIsCameraEvent = false;
2707 }
2708
Jim Millerafef5b22013-10-13 16:49:43 -07002709 AnimatorListenerAdapter mOnScreenAnimationListener = new AnimatorListenerAdapter() {
Jim Millerc162dd02013-09-25 18:57:43 -07002710 @Override
2711 public void onAnimationEnd(Animator animation) {
Jim Millerafef5b22013-10-13 16:49:43 -07002712 mWarpAnimation = null;
2713 if (mTouchState != TOUCH_STATE_SCROLLING && mTouchState != TOUCH_STATE_READY) {
2714 animateWarpPageOffScreen("onScreen end", true);
Jim Millerc162dd02013-09-25 18:57:43 -07002715 }
2716 }
Selim Cineked88cee2014-01-10 17:28:15 -08002717
2718 @Override
2719 public void onAnimationCancel(Animator animation) {
2720 super.onAnimationCancel(animation);
2721 mWarpAnimation = null;
2722 }
Jim Millerc162dd02013-09-25 18:57:43 -07002723 };
2724
Jim Millerafef5b22013-10-13 16:49:43 -07002725 AnimatorListenerAdapter mOffScreenAnimationListener = new AnimatorListenerAdapter() {
2726 @Override
2727 public void onAnimationEnd(Animator animation) {
2728 mWarpAnimation = null;
John Spurlock56d007b2013-10-28 18:40:56 -04002729 mWarpPageExposed = false;
Selim Cineked88cee2014-01-10 17:28:15 -08002730 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
2731 v.setTranslationX(0.0f);
2732 }
2733
2734 @Override
2735 public void onAnimationCancel(Animator animation) {
2736 super.onAnimationCancel(animation);
2737 mWarpAnimation = null;
Jim Millerafef5b22013-10-13 16:49:43 -07002738 }
2739 };
2740
Jim Miller1c058a62013-10-13 19:35:47 -07002741 private void cancelWarpAnimation(String msg, boolean abortAnimation) {
Jim Miller6b0afad2013-10-28 19:08:42 -07002742 if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ",abort=" + abortAnimation + ")");
Jim Miller1c058a62013-10-13 19:35:47 -07002743 if (abortAnimation) {
2744 // We're done with the animation and moving to a new page. Let the scroller
2745 // take over the animation.
Selim Cineked88cee2014-01-10 17:28:15 -08002746 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
Jim Miller1c058a62013-10-13 19:35:47 -07002747 v.animate().cancel();
2748 // Make the scroll amount match the current warp position.
2749 scrollBy(Math.round(-v.getTranslationX()), 0);
2750 v.setTranslationX(0);
2751 } else {
2752 animateWarpPageOffScreen("canceled", true);
2753 }
Jim Millerc162dd02013-09-25 18:57:43 -07002754 }
2755
Jim Millerafef5b22013-10-13 16:49:43 -07002756 private boolean isAnimatingWarpPage() {
2757 return mWarpAnimation != null;
2758 }
2759
Jim Millerc162dd02013-09-25 18:57:43 -07002760 private void animateWarpPageOnScreen(String reason) {
2761 if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")");
Selim Cineked88cee2014-01-10 17:28:15 -08002762 if (!mWarpPageExposed) {
Jim Millerafef5b22013-10-13 16:49:43 -07002763 mWarpPageExposed = true;
John Spurlock56d007b2013-10-28 18:40:56 -04002764 dispatchOnPageBeginWarp();
Selim Cineked88cee2014-01-10 17:28:15 -08002765 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
Jim Millerf4db8f92013-09-20 14:21:50 -07002766 if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX());
Jim Millerc162dd02013-09-25 18:57:43 -07002767 DecelerateInterpolator interp = new DecelerateInterpolator(1.5f);
Selim Cineked88cee2014-01-10 17:28:15 -08002768 int totalOffset = getCurrentWarpOffset();
2769 v.setTranslationX(totalOffset);
Jim Millerafef5b22013-10-13 16:49:43 -07002770 mWarpAnimation = v.animate();
Selim Cineked88cee2014-01-10 17:28:15 -08002771 mWarpAnimation.translationX(mWarpPeekAmount+totalOffset)
Jim Millerc162dd02013-09-25 18:57:43 -07002772 .setInterpolator(interp)
2773 .setDuration(WARP_PEEK_ANIMATION_DURATION)
Jim Millerafef5b22013-10-13 16:49:43 -07002774 .setListener(mOnScreenAnimationListener);
Jim Millerf4db8f92013-09-20 14:21:50 -07002775 }
2776 }
2777
Selim Cineked88cee2014-01-10 17:28:15 -08002778 private int getCurrentWarpOffset() {
2779 if (mCurrentPage == getPageWarpIndex()) {
2780 return 0;
2781 }
2782 View viewRight = getPageAt(mCurrentPage + 1);
2783 View warpView = getPageAt(getPageWarpIndex());
2784 if (viewRight != warpView && viewRight != null && warpView != null) {
2785 return viewRight.getLeft() - warpView.getLeft();
2786 }
2787 return 0;
2788 }
2789
Jim Millerc162dd02013-09-25 18:57:43 -07002790 private void animateWarpPageOffScreen(String reason, boolean animate) {
2791 if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")");
Selim Cineked88cee2014-01-10 17:28:15 -08002792 if (mWarpPageExposed) {
John Spurlock56d007b2013-10-28 18:40:56 -04002793 dispatchOnPageEndWarp();
Selim Cineked88cee2014-01-10 17:28:15 -08002794 KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(getPageWarpIndex());
Jim Millerf4db8f92013-09-20 14:21:50 -07002795 if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX());
Jim Millerc162dd02013-09-25 18:57:43 -07002796 AccelerateInterpolator interp = new AccelerateInterpolator(1.5f);
Selim Cineked88cee2014-01-10 17:28:15 -08002797 int totalOffset = getCurrentWarpOffset();
2798 v.animate().translationX(totalOffset)
Jim Millerc162dd02013-09-25 18:57:43 -07002799 .setInterpolator(interp)
2800 .setDuration(animate ? WARP_PEEK_ANIMATION_DURATION : 0)
Jim Millerafef5b22013-10-13 16:49:43 -07002801 .setListener(mOffScreenAnimationListener);
Jim Millerc162dd02013-09-25 18:57:43 -07002802 } else {
2803 if (DEBUG_WARP) Log.e(TAG, "animateWarpPageOffScreen(): not warping", new Exception());
Jim Millerf4db8f92013-09-20 14:21:50 -07002804 }
2805 }
2806
Jim Millercaf24fc2013-09-10 18:37:01 -07002807 /**
2808 * Swaps the position of the views by setting the left and right edges appropriately.
2809 */
2810 void swapPages(int indexA, int indexB) {
2811 View viewA = getPageAt(indexA);
2812 View viewB = getPageAt(indexB);
2813 if (viewA != viewB && viewA != null && viewB != null) {
2814 int deltaX = viewA.getLeft() - viewB.getLeft();
2815 viewA.offsetLeftAndRight(-deltaX);
2816 viewB.offsetLeftAndRight(deltaX);
2817 }
2818 }
2819
Jim Millerc162dd02013-09-25 18:57:43 -07002820 public void startPageWarp(int pageIndex) {
Jim Millercaf24fc2013-09-10 18:37:01 -07002821 if (DEBUG_WARP) Log.v(TAG, "START WARP");
2822 if (pageIndex != mCurrentPage + 1) {
2823 mPageSwapIndex = mCurrentPage + 1;
2824 }
Jim Millerf4db8f92013-09-20 14:21:50 -07002825 mPageWarpIndex = pageIndex;
2826 }
2827
2828 protected int getPageWarpIndex() {
Selim Cineked88cee2014-01-10 17:28:15 -08002829 return getPageCount() - 1;
Jim Millercaf24fc2013-09-10 18:37:01 -07002830 }
2831
Jim Millerc162dd02013-09-25 18:57:43 -07002832 public void stopPageWarp() {
Jim Millercaf24fc2013-09-10 18:37:01 -07002833 if (DEBUG_WARP) Log.v(TAG, "END WARP");
2834 // mPageSwapIndex is reset in snapToPage() after the scroll animation completes
2835 }
2836
2837 public void onPageBeginWarp() {
2838
2839 }
2840
2841 public void onPageEndWarp() {
2842
2843 }
2844
Jim Millerdcb3d842012-08-23 19:18:12 -07002845}