2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3;
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.LayoutTransition;
23 import android.animation.ObjectAnimator;
24 import android.animation.TimeInterpolator;
25 import android.content.Context;
26 import android.content.res.TypedArray;
27 import android.graphics.Canvas;
28 import android.graphics.Matrix;
29 import android.graphics.Rect;
30 import android.os.Bundle;
31 import android.os.Parcel;
32 import android.os.Parcelable;
33 import android.util.AttributeSet;
34 import android.util.DisplayMetrics;
35 import android.util.Log;
36 import android.view.InputDevice;
37 import android.view.KeyEvent;
38 import android.view.MotionEvent;
39 import android.view.VelocityTracker;
40 import android.view.View;
41 import android.view.ViewConfiguration;
42 import android.view.ViewGroup;
43 import android.view.ViewParent;
44 import android.view.accessibility.AccessibilityEvent;
45 import android.view.accessibility.AccessibilityManager;
46 import android.view.accessibility.AccessibilityNodeInfo;
47 import android.view.animation.Interpolator;
49 import com.android.launcher3.util.Thunk;
51 import java.util.ArrayList;
54 * An abstraction of the original Workspace which supports browsing through a
55 * sequential list of "pages"
57 public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener {
58 private static final String TAG = "PagedView";
59 private static final boolean DEBUG = false;
60 protected static final int INVALID_PAGE = -1;
62 // the min drag distance for a fling to register, to prevent random page shifts
63 private static final int MIN_LENGTH_FOR_FLING = 25;
65 protected static final int PAGE_SNAP_ANIMATION_DURATION = 750;
66 protected static final int OVER_SCROLL_PAGE_SNAP_ANIMATION_DURATION = 350;
67 protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950;
68 protected static final float NANOTIME_DIV = 1000000000.0f;
70 private static final float OVERSCROLL_ACCELERATE_FACTOR = 2;
71 private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
73 private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f;
74 // The page is moved more than halfway, automatically move to the next page on touch up.
75 private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f;
77 private static final float MAX_SCROLL_PROGRESS = 1.0f;
79 // The following constants need to be scaled based on density. The scaled versions will be
80 // assigned to the corresponding member variables below.
81 private static final int FLING_THRESHOLD_VELOCITY = 500;
82 private static final int MIN_SNAP_VELOCITY = 1500;
83 private static final int MIN_FLING_VELOCITY = 250;
85 // We are disabling touch interaction of the widget region for factory ROM.
86 private static final boolean DISABLE_TOUCH_INTERACTION = false;
87 private static final boolean DISABLE_TOUCH_SIDE_PAGES = true;
89 public static final int INVALID_RESTORE_PAGE = -1001;
91 private boolean mFreeScroll = false;
92 private int mFreeScrollMinScrollX = -1;
93 private int mFreeScrollMaxScrollX = -1;
95 static final int AUTOMATIC_PAGE_SPACING = -1;
97 protected int mFlingThresholdVelocity;
98 protected int mMinFlingVelocity;
99 protected int mMinSnapVelocity;
101 protected float mDensity;
102 protected float mSmoothingTime;
103 protected float mTouchX;
105 protected boolean mFirstLayout = true;
106 private int mNormalChildHeight;
108 protected int mCurrentPage;
109 protected int mRestorePage = INVALID_RESTORE_PAGE;
110 protected int mChildCountOnLastLayout;
112 protected int mNextPage = INVALID_PAGE;
113 protected int mMaxScrollX;
114 protected LauncherScroller mScroller;
115 private Interpolator mDefaultInterpolator;
116 private VelocityTracker mVelocityTracker;
117 @Thunk int mPageSpacing = 0;
119 private float mParentDownMotionX;
120 private float mParentDownMotionY;
121 private float mDownMotionX;
122 private float mDownMotionY;
123 private float mDownScrollX;
124 private float mDragViewBaselineLeft;
125 protected float mLastMotionX;
126 protected float mLastMotionXRemainder;
127 protected float mLastMotionY;
128 protected float mTotalMotionX;
129 private int mLastScreenCenter = -1;
131 private boolean mCancelTap;
133 private int[] mPageScrolls;
135 protected final static int TOUCH_STATE_REST = 0;
136 protected final static int TOUCH_STATE_SCROLLING = 1;
137 protected final static int TOUCH_STATE_PREV_PAGE = 2;
138 protected final static int TOUCH_STATE_NEXT_PAGE = 3;
139 protected final static int TOUCH_STATE_REORDERING = 4;
141 protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f;
143 protected int mTouchState = TOUCH_STATE_REST;
144 protected boolean mForceScreenScrolled = false;
146 protected OnLongClickListener mLongClickListener;
148 protected int mTouchSlop;
149 private int mPagingTouchSlop;
150 private int mMaximumVelocity;
151 protected int mPageLayoutWidthGap;
152 protected int mPageLayoutHeightGap;
153 protected int mCellCountX = 0;
154 protected int mCellCountY = 0;
155 protected boolean mCenterPagesVertically;
156 protected boolean mAllowOverScroll = true;
157 protected int mUnboundedScrollX;
158 protected int[] mTempVisiblePagesRange = new int[2];
159 protected boolean mForceDrawAllChildrenNextFrame;
161 // mOverScrollX is equal to getScrollX() when we're within the normal scroll range. Otherwise
162 // it is equal to the scaled overscroll position. We use a separate value so as to prevent
163 // the screens from continuing to translate beyond the normal bounds.
164 protected int mOverScrollX;
166 protected static final int INVALID_POINTER = -1;
168 protected int mActivePointerId = INVALID_POINTER;
170 private PageSwitchListener mPageSwitchListener;
172 // If true, modify alpha of neighboring pages as user scrolls left/right
173 protected boolean mFadeInAdjacentScreens = false;
175 // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding
176 // to switch to a new page
177 protected boolean mUsePagingTouchSlop = true;
179 // If true, the subclass should directly update scrollX itself in its computeScroll method
180 // (SmoothPagedView does this)
181 protected boolean mDeferScrollUpdate = false;
183 protected boolean mIsPageMoving = false;
185 private boolean mWasInOverscroll = false;
188 @Thunk int mPageIndicatorViewId;
189 @Thunk PageIndicator mPageIndicator;
190 // The viewport whether the pages are to be contained (the actual view may be larger than the
192 private Rect mViewport = new Rect();
195 // We use the min scale to determine how much to expand the actually PagedView measured
196 // dimensions such that when we are zoomed out, the view is not clipped
197 private static int REORDERING_DROP_REPOSITION_DURATION = 200;
198 private static int REORDERING_REORDER_REPOSITION_DURATION = 300;
199 private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80;
201 private float mMinScale = 1f;
202 private boolean mUseMinScale = false;
203 protected View mDragView;
204 protected AnimatorSet mZoomInOutAnim;
205 private Runnable mSidePageHoverRunnable;
206 @Thunk int mSidePageHoverIndex = -1;
207 // This variable's scope is only for the duration of startReordering() and endReordering()
208 private boolean mReorderingStarted = false;
209 // This variable's scope is for the duration of startReordering() and after the zoomIn()
210 // animation after endReordering()
211 private boolean mIsReordering;
212 // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition
213 private int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2;
214 private int mPostReorderingPreZoomInRemainingAnimationCount;
215 private Runnable mPostReorderingPreZoomInRunnable;
217 // Convenience/caching
218 private static final Matrix sTmpInvMatrix = new Matrix();
219 private static final float[] sTmpPoint = new float[2];
220 private static final int[] sTmpIntPoint = new int[2];
221 private static final Rect sTmpRect = new Rect();
223 protected final Rect mInsets = new Rect();
224 protected final boolean mIsRtl;
226 public interface PageSwitchListener {
227 void onPageSwitch(View newPage, int newPageIndex);
230 public PagedView(Context context) {
234 public PagedView(Context context, AttributeSet attrs) {
235 this(context, attrs, 0);
238 public PagedView(Context context, AttributeSet attrs, int defStyle) {
239 super(context, attrs, defStyle);
241 TypedArray a = context.obtainStyledAttributes(attrs,
242 R.styleable.PagedView, defStyle, 0);
244 mPageLayoutWidthGap = a.getDimensionPixelSize(
245 R.styleable.PagedView_pageLayoutWidthGap, 0);
246 mPageLayoutHeightGap = a.getDimensionPixelSize(
247 R.styleable.PagedView_pageLayoutHeightGap, 0);
248 mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1);
251 setHapticFeedbackEnabled(false);
252 mIsRtl = Utilities.isRtl(getResources());
257 * Initializes various states for this workspace.
259 protected void init() {
260 mScroller = new LauncherScroller(getContext());
261 setDefaultInterpolator(new ScrollInterpolator());
263 mCenterPagesVertically = true;
265 final ViewConfiguration configuration = ViewConfiguration.get(getContext());
266 mTouchSlop = configuration.getScaledPagingTouchSlop();
267 mPagingTouchSlop = configuration.getScaledPagingTouchSlop();
268 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
269 mDensity = getResources().getDisplayMetrics().density;
271 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * mDensity);
272 mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * mDensity);
273 mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * mDensity);
274 setOnHierarchyChangeListener(this);
277 protected void setDefaultInterpolator(Interpolator interpolator) {
278 mDefaultInterpolator = interpolator;
279 mScroller.setInterpolator(mDefaultInterpolator);
282 protected void onAttachedToWindow() {
283 super.onAttachedToWindow();
285 // Hook up the page indicator
286 ViewGroup parent = (ViewGroup) getParent();
287 ViewGroup grandParent = (ViewGroup) parent.getParent();
288 if (mPageIndicator == null && mPageIndicatorViewId > -1) {
289 mPageIndicator = (PageIndicator) grandParent.findViewById(mPageIndicatorViewId);
290 mPageIndicator.removeAllMarkers(true);
292 ArrayList<PageIndicator.PageMarkerResources> markers =
293 new ArrayList<PageIndicator.PageMarkerResources>();
294 for (int i = 0; i < getChildCount(); ++i) {
295 markers.add(getPageIndicatorMarker(i));
298 mPageIndicator.addMarkers(markers, true);
300 OnClickListener listener = getPageIndicatorClickListener();
301 if (listener != null) {
302 mPageIndicator.setOnClickListener(listener);
304 mPageIndicator.setContentDescription(getPageIndicatorDescription());
308 protected String getPageIndicatorDescription() {
309 return getCurrentPageDescription();
312 protected OnClickListener getPageIndicatorClickListener() {
317 protected void onDetachedFromWindow() {
318 super.onDetachedFromWindow();
319 // Unhook the page indicator
320 mPageIndicator = null;
323 // Convenience methods to map points from self to parent and vice versa
324 private float[] mapPointFromViewToParent(View v, float x, float y) {
327 v.getMatrix().mapPoints(sTmpPoint);
328 sTmpPoint[0] += v.getLeft();
329 sTmpPoint[1] += v.getTop();
332 private float[] mapPointFromParentToView(View v, float x, float y) {
333 sTmpPoint[0] = x - v.getLeft();
334 sTmpPoint[1] = y - v.getTop();
335 v.getMatrix().invert(sTmpInvMatrix);
336 sTmpInvMatrix.mapPoints(sTmpPoint);
340 private void updateDragViewTranslationDuringDrag() {
341 if (mDragView != null) {
342 float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) +
343 (mDragViewBaselineLeft - mDragView.getLeft());
344 float y = mLastMotionY - mDownMotionY;
345 mDragView.setTranslationX(x);
346 mDragView.setTranslationY(y);
348 if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): "
353 public void setMinScale(float f) {
360 public void setScaleX(float scaleX) {
361 super.setScaleX(scaleX);
362 if (isReordering(true)) {
363 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
366 updateDragViewTranslationDuringDrag();
370 // Convenience methods to get the actual width/height of the PagedView (since it is measured
371 // to be larger to account for the minimum possible scale)
372 int getViewportWidth() {
373 return mViewport.width();
375 int getViewportHeight() {
376 return mViewport.height();
379 // Convenience methods to get the offset ASSUMING that we are centering the pages in the
380 // PagedView both horizontally and vertically
381 int getViewportOffsetX() {
382 return (getMeasuredWidth() - getViewportWidth()) / 2;
385 int getViewportOffsetY() {
386 return (getMeasuredHeight() - getViewportHeight()) / 2;
389 PageIndicator getPageIndicator() {
390 return mPageIndicator;
392 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) {
393 return new PageIndicator.PageMarkerResources();
397 * Add a page change listener which will be called when a page is _finished_ listening.
400 public void setPageSwitchListener(PageSwitchListener pageSwitchListener) {
401 mPageSwitchListener = pageSwitchListener;
402 if (mPageSwitchListener != null) {
403 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage);
408 * Returns the index of the currently displayed page.
410 public int getCurrentPage() {
415 * Returns the index of page to be shown immediately afterwards.
418 return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
422 return getChildCount();
425 public View getPageAt(int index) {
426 return getChildAt(index);
429 protected int indexToPage(int index) {
434 * Updates the scroll of the current page immediately to its final scroll position. We use this
435 * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
436 * the previous tab page.
438 protected void updateCurrentPageScroll() {
439 // If the current page is invalid, just reset the scroll position to zero
441 if (0 <= mCurrentPage && mCurrentPage < getPageCount()) {
442 newX = getScrollForPage(mCurrentPage);
445 mScroller.setFinalX(newX);
446 forceFinishScroller();
449 private void abortScrollerAnimation(boolean resetNextPage) {
450 mScroller.abortAnimation();
451 // We need to clean up the next page here to avoid computeScrollHelper from
452 // updating current page on the pass.
454 mNextPage = INVALID_PAGE;
458 private void forceFinishScroller() {
459 mScroller.forceFinished(true);
460 // We need to clean up the next page here to avoid computeScrollHelper from
461 // updating current page on the pass.
462 mNextPage = INVALID_PAGE;
465 private int validateNewPage(int newPage) {
466 int validatedPage = newPage;
467 // When in free scroll mode, we need to clamp to the free scroll page range.
469 getFreeScrollPageRange(mTempVisiblePagesRange);
470 validatedPage = Math.max(mTempVisiblePagesRange[0],
471 Math.min(newPage, mTempVisiblePagesRange[1]));
473 // Ensure that it is clamped by the actual set of children in all cases
474 validatedPage = Math.max(0, Math.min(validatedPage, getPageCount() - 1));
475 return validatedPage;
479 * Sets the current page.
481 public void setCurrentPage(int currentPage) {
482 if (!mScroller.isFinished()) {
483 abortScrollerAnimation(true);
485 // don't introduce any checks like mCurrentPage == currentPage here-- if we change the
487 if (getChildCount() == 0) {
490 mForceScreenScrolled = true;
491 mCurrentPage = validateNewPage(currentPage);
492 updateCurrentPageScroll();
493 notifyPageSwitchListener();
498 * The restore page will be set in place of the current page at the next (likely first)
501 void setRestorePage(int restorePage) {
502 mRestorePage = restorePage;
504 int getRestorePage() {
509 * Should be called whenever the page changes. In the case of a scroll, we wait until the page
512 protected void notifyPageSwitchListener() {
513 if (mPageSwitchListener != null) {
514 mPageSwitchListener.onPageSwitch(getPageAt(getNextPage()), getNextPage());
517 updatePageIndicator();
520 private void updatePageIndicator() {
521 // Update the page indicator (when we aren't reordering)
522 if (mPageIndicator != null) {
523 mPageIndicator.setContentDescription(getPageIndicatorDescription());
524 if (!isReordering(false)) {
525 mPageIndicator.setActiveMarker(getNextPage());
529 protected void pageBeginMoving() {
530 if (!mIsPageMoving) {
531 mIsPageMoving = true;
536 protected void pageEndMoving() {
538 mIsPageMoving = false;
543 protected boolean isPageMoving() {
544 return mIsPageMoving;
547 // a method that subclasses can override to add behavior
548 protected void onPageBeginMoving() {
551 // a method that subclasses can override to add behavior
552 protected void onPageEndMoving() {
553 mWasInOverscroll = false;
557 * Registers the specified listener on each page contained in this workspace.
559 * @param l The listener used to respond to long clicks.
562 public void setOnLongClickListener(OnLongClickListener l) {
563 mLongClickListener = l;
564 final int count = getPageCount();
565 for (int i = 0; i < count; i++) {
566 getPageAt(i).setOnLongClickListener(l);
568 super.setOnLongClickListener(l);
572 public void scrollBy(int x, int y) {
573 scrollTo(mUnboundedScrollX + x, getScrollY() + y);
577 public void scrollTo(int x, int y) {
578 // In free scroll mode, we clamp the scrollX
580 x = Math.min(x, mFreeScrollMaxScrollX);
581 x = Math.max(x, mFreeScrollMinScrollX);
584 mUnboundedScrollX = x;
586 boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0);
587 boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX);
588 if (isXBeforeFirstPage) {
589 super.scrollTo(0, y);
590 if (mAllowOverScroll) {
591 mWasInOverscroll = true;
593 overScroll(x - mMaxScrollX);
598 } else if (isXAfterLastPage) {
599 super.scrollTo(mMaxScrollX, y);
600 if (mAllowOverScroll) {
601 mWasInOverscroll = true;
605 overScroll(x - mMaxScrollX);
609 if (mWasInOverscroll) {
611 mWasInOverscroll = false;
614 super.scrollTo(x, y);
618 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
620 // Update the last motion events when scrolling
621 if (isReordering(true)) {
622 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY);
625 updateDragViewTranslationDuringDrag();
629 private void sendScrollAccessibilityEvent() {
630 AccessibilityManager am =
631 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
632 if (am.isEnabled()) {
633 if (mCurrentPage != getNextPage()) {
634 AccessibilityEvent ev =
635 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
637 sendAccessibilityEventUnchecked(ev);
642 // we moved this functionality to a helper function so SmoothPagedView can reuse it
643 protected boolean computeScrollHelper() {
644 if (mScroller.computeScrollOffset()) {
645 // Don't bother scrolling if the page does not need to be moved
646 if (getScrollX() != mScroller.getCurrX()
647 || getScrollY() != mScroller.getCurrY()
648 || mOverScrollX != mScroller.getCurrX()) {
649 float scaleX = mFreeScroll ? getScaleX() : 1f;
650 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX));
651 scrollTo(scrollX, mScroller.getCurrY());
655 } else if (mNextPage != INVALID_PAGE) {
656 sendScrollAccessibilityEvent();
658 mCurrentPage = validateNewPage(mNextPage);
659 mNextPage = INVALID_PAGE;
660 notifyPageSwitchListener();
662 // We don't want to trigger a page end moving unless the page has settled
663 // and the user has stopped scrolling
664 if (mTouchState == TOUCH_STATE_REST) {
668 onPostReorderingAnimationCompleted();
669 AccessibilityManager am = (AccessibilityManager)
670 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
671 if (am.isEnabled()) {
672 // Notify the user when the page changes
673 announceForAccessibility(getCurrentPageDescription());
681 public void computeScroll() {
682 computeScrollHelper();
685 public static class LayoutParams extends ViewGroup.LayoutParams {
686 public boolean isFullScreenPage = false;
691 public LayoutParams(int width, int height) {
692 super(width, height);
695 public LayoutParams(Context context, AttributeSet attrs) {
696 super(context, attrs);
699 public LayoutParams(ViewGroup.LayoutParams source) {
705 public LayoutParams generateLayoutParams(AttributeSet attrs) {
706 return new LayoutParams(getContext(), attrs);
710 protected LayoutParams generateDefaultLayoutParams() {
711 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
715 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
716 return new LayoutParams(p);
720 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
721 return p instanceof LayoutParams;
724 public void addFullScreenPage(View page) {
725 LayoutParams lp = generateDefaultLayoutParams();
726 lp.isFullScreenPage = true;
727 super.addView(page, 0, lp);
730 public int getNormalChildHeight() {
731 return mNormalChildHeight;
735 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
736 if (getChildCount() == 0) {
737 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
741 // We measure the dimensions of the PagedView to be larger than the pages so that when we
742 // zoom out (and scale down), the view is still contained in the parent
743 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
744 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
745 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
746 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
747 // NOTE: We multiply by 2f to account for the fact that depending on the offset of the
748 // viewport, we can be at most one and a half screens offset once we scale down
749 DisplayMetrics dm = getResources().getDisplayMetrics();
750 int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right,
751 dm.heightPixels + mInsets.top + mInsets.bottom);
753 int parentWidthSize = (int) (2f * maxSize);
754 int parentHeightSize = (int) (2f * maxSize);
755 int scaledWidthSize, scaledHeightSize;
757 scaledWidthSize = (int) (parentWidthSize / mMinScale);
758 scaledHeightSize = (int) (parentHeightSize / mMinScale);
760 scaledWidthSize = widthSize;
761 scaledHeightSize = heightSize;
763 mViewport.set(0, 0, widthSize, heightSize);
765 if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) {
766 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
770 // Return early if we aren't given a proper dimension
771 if (widthSize <= 0 || heightSize <= 0) {
772 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
776 /* Allow the height to be set as WRAP_CONTENT. This allows the particular case
777 * of the All apps view on XLarge displays to not take up more space then it needs. Width
778 * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect
779 * each page to have the same width.
781 final int verticalPadding = getPaddingTop() + getPaddingBottom();
782 final int horizontalPadding = getPaddingLeft() + getPaddingRight();
784 int referenceChildWidth = 0;
785 // The children are given the same width and height as the workspace
786 // unless they were set to WRAP_CONTENT
787 if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize);
788 if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize);
789 if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize);
790 if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding);
791 if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding);
792 final int childCount = getChildCount();
793 for (int i = 0; i < childCount; i++) {
794 // disallowing padding in paged view (just pass 0)
795 final View child = getPageAt(i);
796 if (child.getVisibility() != GONE) {
797 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
804 if (!lp.isFullScreenPage) {
805 if (lp.width == LayoutParams.WRAP_CONTENT) {
806 childWidthMode = MeasureSpec.AT_MOST;
808 childWidthMode = MeasureSpec.EXACTLY;
811 if (lp.height == LayoutParams.WRAP_CONTENT) {
812 childHeightMode = MeasureSpec.AT_MOST;
814 childHeightMode = MeasureSpec.EXACTLY;
817 childWidth = getViewportWidth() - horizontalPadding
818 - mInsets.left - mInsets.right;
819 childHeight = getViewportHeight() - verticalPadding
820 - mInsets.top - mInsets.bottom;
821 mNormalChildHeight = childHeight;
823 childWidthMode = MeasureSpec.EXACTLY;
824 childHeightMode = MeasureSpec.EXACTLY;
826 childWidth = getViewportWidth() - mInsets.left - mInsets.right;
827 childHeight = getViewportHeight();
829 if (referenceChildWidth == 0) {
830 referenceChildWidth = childWidth;
833 final int childWidthMeasureSpec =
834 MeasureSpec.makeMeasureSpec(childWidth, childWidthMode);
835 final int childHeightMeasureSpec =
836 MeasureSpec.makeMeasureSpec(childHeight, childHeightMode);
837 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
840 setMeasuredDimension(scaledWidthSize, scaledHeightSize);
844 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
845 if (getChildCount() == 0) {
849 if (DEBUG) Log.d(TAG, "PagedView.onLayout()");
850 final int childCount = getChildCount();
852 int offsetX = getViewportOffsetX();
853 int offsetY = getViewportOffsetY();
855 // Update the viewport offsets
856 mViewport.offset(offsetX, offsetY);
858 final int startIndex = mIsRtl ? childCount - 1 : 0;
859 final int endIndex = mIsRtl ? -1 : childCount;
860 final int delta = mIsRtl ? -1 : 1;
862 int verticalPadding = getPaddingTop() + getPaddingBottom();
864 LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams();
867 int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft());
868 if (mPageScrolls == null || childCount != mChildCountOnLastLayout) {
869 mPageScrolls = new int[childCount];
872 for (int i = startIndex; i != endIndex; i += delta) {
873 final View child = getPageAt(i);
874 if (child.getVisibility() != View.GONE) {
875 lp = (LayoutParams) child.getLayoutParams();
877 if (lp.isFullScreenPage) {
880 childTop = offsetY + getPaddingTop() + mInsets.top;
881 if (mCenterPagesVertically) {
882 childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2;
886 final int childWidth = child.getMeasuredWidth();
887 final int childHeight = child.getMeasuredHeight();
889 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop);
890 child.layout(childLeft, childTop,
891 childLeft + child.getMeasuredWidth(), childTop + childHeight);
893 int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft();
894 mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX;
896 int pageGap = mPageSpacing;
897 int next = i + delta;
898 if (next != endIndex) {
899 nextLp = (LayoutParams) getPageAt(next).getLayoutParams();
904 // Prevent full screen pages from showing in the viewport
905 // when they are not the current page.
906 if (lp.isFullScreenPage) {
907 pageGap = getPaddingLeft();
908 } else if (nextLp != null && nextLp.isFullScreenPage) {
909 pageGap = getPaddingRight();
912 childLeft += childWidth + pageGap + getChildGap();
916 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < childCount) {
917 updateCurrentPageScroll();
918 mFirstLayout = false;
921 final LayoutTransition transition = getLayoutTransition();
922 // If the transition is running defer updating max scroll, as some empty pages could
923 // still be present, and a max scroll change could cause sudden jumps in scroll.
924 if (transition != null && transition.isRunning()) {
925 transition.addTransitionListener(new LayoutTransition.TransitionListener() {
928 public void startTransition(LayoutTransition transition, ViewGroup container,
929 View view, int transitionType) { }
932 public void endTransition(LayoutTransition transition, ViewGroup container,
933 View view, int transitionType) {
934 // Wait until all transitions are complete.
935 if (!transition.isRunning()) {
936 transition.removeTransitionListener(this);
945 if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) {
946 if (mRestorePage != INVALID_RESTORE_PAGE) {
947 setCurrentPage(mRestorePage);
948 mRestorePage = INVALID_RESTORE_PAGE;
950 setCurrentPage(getNextPage());
953 mChildCountOnLastLayout = childCount;
955 if (isReordering(true)) {
956 updateDragViewTranslationDuringDrag();
960 protected int getChildGap() {
964 private void updateMaxScrollX() {
965 int childCount = getChildCount();
966 if (childCount > 0) {
967 final int index = mIsRtl ? 0 : childCount - 1;
968 mMaxScrollX = getScrollForPage(index);
974 public void setPageSpacing(int pageSpacing) {
975 mPageSpacing = pageSpacing;
979 protected void screenScrolled(int screenCenter) {
980 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX;
982 if (mFadeInAdjacentScreens && !isInOverscroll) {
983 for (int i = 0; i < getChildCount(); i++) {
984 View child = getChildAt(i);
986 float scrollProgress = getScrollProgress(screenCenter, child, i);
987 float alpha = 1 - Math.abs(scrollProgress);
988 child.setAlpha(alpha);
996 public void onChildViewAdded(View parent, View child) {
997 // Update the page indicator, we don't update the page indicator as we
999 if (mPageIndicator != null && !isReordering(false)) {
1000 int pageIndex = indexOfChild(child);
1001 mPageIndicator.addMarker(pageIndex,
1002 getPageIndicatorMarker(pageIndex),
1006 // This ensures that when children are added, they get the correct transforms / alphas
1007 // in accordance with any scroll effects.
1008 mForceScreenScrolled = true;
1009 updateFreescrollBounds();
1014 public void onChildViewRemoved(View parent, View child) {
1015 mForceScreenScrolled = true;
1016 updateFreescrollBounds();
1020 private void removeMarkerForView(int index) {
1021 // Update the page indicator, we don't update the page indicator as we
1023 if (mPageIndicator != null && !isReordering(false)) {
1024 mPageIndicator.removeMarker(index, true);
1029 public void removeView(View v) {
1030 // XXX: We should find a better way to hook into this before the view
1031 // gets removed form its parent...
1032 removeMarkerForView(indexOfChild(v));
1033 super.removeView(v);
1036 public void removeViewInLayout(View v) {
1037 // XXX: We should find a better way to hook into this before the view
1038 // gets removed form its parent...
1039 removeMarkerForView(indexOfChild(v));
1040 super.removeViewInLayout(v);
1043 public void removeViewAt(int index) {
1044 // XXX: We should find a better way to hook into this before the view
1045 // gets removed form its parent...
1046 removeViewAt(index);
1047 super.removeViewAt(index);
1050 public void removeAllViewsInLayout() {
1051 // Update the page indicator, we don't update the page indicator as we
1053 if (mPageIndicator != null) {
1054 mPageIndicator.removeAllMarkers(true);
1057 super.removeAllViewsInLayout();
1060 protected int getChildOffset(int index) {
1061 if (index < 0 || index > getChildCount() - 1) return 0;
1063 int offset = getPageAt(index).getLeft() - getViewportOffsetX();
1068 protected void getFreeScrollPageRange(int[] range) {
1070 range[1] = Math.max(0, getChildCount() - 1);
1073 protected void getVisiblePages(int[] range) {
1074 final int pageCount = getChildCount();
1075 sTmpIntPoint[0] = sTmpIntPoint[1] = 0;
1080 if (pageCount > 0) {
1081 int viewportWidth = getViewportWidth();
1084 int count = getChildCount();
1085 for (int i = 0; i < count; i++) {
1086 View currPage = getPageAt(i);
1088 sTmpIntPoint[0] = 0;
1089 Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false);
1090 if (sTmpIntPoint[0] > viewportWidth) {
1091 if (range[0] == -1) {
1098 sTmpIntPoint[0] = currPage.getMeasuredWidth();
1099 Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false);
1100 if (sTmpIntPoint[0] < 0) {
1101 if (range[0] == -1) {
1109 range[0] = curScreen;
1113 range[1] = curScreen;
1120 protected boolean shouldDrawChild(View child) {
1121 return child.getVisibility() == VISIBLE;
1125 protected void dispatchDraw(Canvas canvas) {
1126 // Find out which screens are visible; as an optimization we only call draw on them
1127 final int pageCount = getChildCount();
1128 if (pageCount > 0) {
1129 int halfScreenSize = getViewportWidth() / 2;
1130 // mOverScrollX is equal to getScrollX() when we're within the normal scroll range.
1131 // Otherwise it is equal to the scaled overscroll position.
1132 int screenCenter = mOverScrollX + halfScreenSize;
1134 if (screenCenter != mLastScreenCenter || mForceScreenScrolled) {
1135 // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can
1136 // set it for the next frame
1137 mForceScreenScrolled = false;
1138 screenScrolled(screenCenter);
1139 mLastScreenCenter = screenCenter;
1142 getVisiblePages(mTempVisiblePagesRange);
1143 final int leftScreen = mTempVisiblePagesRange[0];
1144 final int rightScreen = mTempVisiblePagesRange[1];
1145 if (leftScreen != -1 && rightScreen != -1) {
1146 final long drawingTime = getDrawingTime();
1147 // Clip to the bounds
1149 canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(),
1150 getScrollY() + getBottom() - getTop());
1152 // Draw all the children, leaving the drag view for last
1153 for (int i = pageCount - 1; i >= 0; i--) {
1154 final View v = getPageAt(i);
1155 if (v == mDragView) continue;
1156 if (mForceDrawAllChildrenNextFrame ||
1157 (leftScreen <= i && i <= rightScreen && shouldDrawChild(v))) {
1158 drawChild(canvas, v, drawingTime);
1161 // Draw the drag view on top (if there is one)
1162 if (mDragView != null) {
1163 drawChild(canvas, mDragView, drawingTime);
1166 mForceDrawAllChildrenNextFrame = false;
1173 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
1174 int page = indexToPage(indexOfChild(child));
1175 if (page != mCurrentPage || !mScroller.isFinished()) {
1183 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
1185 if (mNextPage != INVALID_PAGE) {
1186 focusablePage = mNextPage;
1188 focusablePage = mCurrentPage;
1190 View v = getPageAt(focusablePage);
1192 return v.requestFocus(direction, previouslyFocusedRect);
1198 public boolean dispatchUnhandledMove(View focused, int direction) {
1199 // XXX-RTL: This will be fixed in a future CL
1200 if (direction == View.FOCUS_LEFT) {
1201 if (getCurrentPage() > 0) {
1202 snapToPage(getCurrentPage() - 1);
1205 } else if (direction == View.FOCUS_RIGHT) {
1206 if (getCurrentPage() < getPageCount() - 1) {
1207 snapToPage(getCurrentPage() + 1);
1211 return super.dispatchUnhandledMove(focused, direction);
1215 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1216 // XXX-RTL: This will be fixed in a future CL
1217 if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) {
1218 getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode);
1220 if (direction == View.FOCUS_LEFT) {
1221 if (mCurrentPage > 0) {
1222 getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode);
1224 } else if (direction == View.FOCUS_RIGHT){
1225 if (mCurrentPage < getPageCount() - 1) {
1226 getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode);
1232 * If one of our descendant views decides that it could be focused now, only
1233 * pass that along if it's on the current page.
1235 * This happens when live folders requery, and if they're off page, they
1236 * end up calling requestFocus, which pulls it on page.
1239 public void focusableViewAvailable(View focused) {
1240 View current = getPageAt(mCurrentPage);
1244 super.focusableViewAvailable(focused);
1250 ViewParent parent = v.getParent();
1251 if (parent instanceof View) {
1252 v = (View)v.getParent();
1263 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1264 if (disallowIntercept) {
1265 // We need to make sure to cancel our long press if
1266 // a scrollable widget takes over touch events
1267 final View currentPage = getPageAt(mCurrentPage);
1268 currentPage.cancelLongPress();
1270 super.requestDisallowInterceptTouchEvent(disallowIntercept);
1274 * Return true if a tap at (x, y) should trigger a flip to the previous page.
1276 protected boolean hitsPreviousPage(float x, float y) {
1278 return (x > (getViewportOffsetX() + getViewportWidth() -
1279 getPaddingRight() - mPageSpacing));
1281 return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
1285 * Return true if a tap at (x, y) should trigger a flip to the next page.
1287 protected boolean hitsNextPage(float x, float y) {
1289 return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing);
1291 return (x > (getViewportOffsetX() + getViewportWidth() -
1292 getPaddingRight() - mPageSpacing));
1295 /** Returns whether x and y originated within the buffered viewport */
1296 private boolean isTouchPointInViewportWithBuffer(int x, int y) {
1297 sTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top,
1298 mViewport.right + mViewport.width() / 2, mViewport.bottom);
1299 return sTmpRect.contains(x, y);
1303 public boolean onInterceptTouchEvent(MotionEvent ev) {
1304 if (DISABLE_TOUCH_INTERACTION) {
1309 * This method JUST determines whether we want to intercept the motion.
1310 * If we return true, onTouchEvent will be called and we do the actual
1313 acquireVelocityTrackerAndAddMovement(ev);
1315 // Skip touch handling if there are no pages to swipe
1316 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev);
1319 * Shortcut the most recurring case: the user is in the dragging
1320 * state and he is moving his finger. We want to intercept this
1323 final int action = ev.getAction();
1324 if ((action == MotionEvent.ACTION_MOVE) &&
1325 (mTouchState == TOUCH_STATE_SCROLLING)) {
1329 switch (action & MotionEvent.ACTION_MASK) {
1330 case MotionEvent.ACTION_MOVE: {
1332 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1333 * whether the user has moved far enough from his original down touch.
1335 if (mActivePointerId != INVALID_POINTER) {
1336 determineScrollingStart(ev);
1338 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN
1339 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN
1340 // i.e. fall through to the next case (don't break)
1341 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events
1342 // while it's small- this was causing a crash before we checked for INVALID_POINTER)
1346 case MotionEvent.ACTION_DOWN: {
1347 final float x = ev.getX();
1348 final float y = ev.getY();
1349 // Remember location of down touch
1352 mDownScrollX = getScrollX();
1355 float[] p = mapPointFromViewToParent(this, x, y);
1356 mParentDownMotionX = p[0];
1357 mParentDownMotionY = p[1];
1358 mLastMotionXRemainder = 0;
1360 mActivePointerId = ev.getPointerId(0);
1363 * If being flinged and user touches the screen, initiate drag;
1364 * otherwise don't. mScroller.isFinished should be false when
1367 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX());
1368 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3);
1370 if (finishedScrolling) {
1371 mTouchState = TOUCH_STATE_REST;
1372 if (!mScroller.isFinished() && !mFreeScroll) {
1373 setCurrentPage(getNextPage());
1377 if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) {
1378 mTouchState = TOUCH_STATE_SCROLLING;
1380 mTouchState = TOUCH_STATE_REST;
1384 // check if this can be the beginning of a tap on the side of the pages
1385 // to scroll the current page
1386 if (!DISABLE_TOUCH_SIDE_PAGES) {
1387 if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) {
1388 if (getChildCount() > 0) {
1389 if (hitsPreviousPage(x, y)) {
1390 mTouchState = TOUCH_STATE_PREV_PAGE;
1391 } else if (hitsNextPage(x, y)) {
1392 mTouchState = TOUCH_STATE_NEXT_PAGE;
1400 case MotionEvent.ACTION_UP:
1401 case MotionEvent.ACTION_CANCEL:
1405 case MotionEvent.ACTION_POINTER_UP:
1406 onSecondaryPointerUp(ev);
1407 releaseVelocityTracker();
1412 * The only time we want to intercept motion events is if we are in the
1415 return mTouchState != TOUCH_STATE_REST;
1418 protected void determineScrollingStart(MotionEvent ev) {
1419 determineScrollingStart(ev, 1.0f);
1423 * Determines if we should change the touch state to start scrolling after the
1424 * user moves their touch point too far.
1426 protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
1427 // Disallow scrolling if we don't have a valid pointer index
1428 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1429 if (pointerIndex == -1) return;
1431 // Disallow scrolling if we started the gesture from outside the viewport
1432 final float x = ev.getX(pointerIndex);
1433 final float y = ev.getY(pointerIndex);
1434 if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return;
1436 final int xDiff = (int) Math.abs(x - mLastMotionX);
1437 final int yDiff = (int) Math.abs(y - mLastMotionY);
1439 final int touchSlop = Math.round(touchSlopScale * mTouchSlop);
1440 boolean xPaged = xDiff > mPagingTouchSlop;
1441 boolean xMoved = xDiff > touchSlop;
1442 boolean yMoved = yDiff > touchSlop;
1444 if (xMoved || xPaged || yMoved) {
1445 if (mUsePagingTouchSlop ? xPaged : xMoved) {
1446 // Scroll if the user moved far enough along the X axis
1447 mTouchState = TOUCH_STATE_SCROLLING;
1448 mTotalMotionX += Math.abs(mLastMotionX - x);
1450 mLastMotionXRemainder = 0;
1451 mTouchX = getViewportOffsetX() + getScrollX();
1452 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1453 onScrollInteractionBegin();
1459 protected void cancelCurrentPageLongPress() {
1460 // Try canceling the long press. It could also have been scheduled
1461 // by a distant descendant, so use the mAllowLongPress flag to block
1463 final View currentPage = getPageAt(mCurrentPage);
1464 if (currentPage != null) {
1465 currentPage.cancelLongPress();
1469 protected float getScrollProgress(int screenCenter, View v, int page) {
1470 final int halfScreenSize = getViewportWidth() / 2;
1472 int delta = screenCenter - (getScrollForPage(page) + halfScreenSize);
1473 int count = getChildCount();
1475 final int totalDistance;
1477 int adjacentPage = page + 1;
1478 if ((delta < 0 && !mIsRtl) || (delta > 0 && mIsRtl)) {
1479 adjacentPage = page - 1;
1482 if (adjacentPage < 0 || adjacentPage > count - 1) {
1483 totalDistance = v.getMeasuredWidth() + mPageSpacing;
1485 totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page));
1488 float scrollProgress = delta / (totalDistance * 1.0f);
1489 scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS);
1490 scrollProgress = Math.max(scrollProgress, - MAX_SCROLL_PROGRESS);
1491 return scrollProgress;
1494 public int getScrollForPage(int index) {
1495 if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
1498 return mPageScrolls[index];
1502 // While layout transitions are occurring, a child's position may stray from its baseline
1503 // position. This method returns the magnitude of this stray at any given time.
1504 public int getLayoutTransitionOffsetForPage(int index) {
1505 if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) {
1508 View child = getChildAt(index);
1510 int scrollOffset = 0;
1511 LayoutParams lp = (LayoutParams) child.getLayoutParams();
1512 if (!lp.isFullScreenPage) {
1513 scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft();
1516 int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX();
1517 return (int) (child.getX() - baselineX);
1521 // This curve determines how the effect of scrolling over the limits of the page dimishes
1522 // as the user pulls further and further from the bounds
1523 private float overScrollInfluenceCurve(float f) {
1525 return f * f * f + 1.0f;
1528 protected float acceleratedOverFactor(float amount) {
1529 int screenSize = getViewportWidth();
1531 // We want to reach the max over scroll effect when the user has
1532 // over scrolled half the size of the screen
1533 float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize);
1535 if (f == 0) return 0;
1537 // Clamp this factor, f, to -1 < f < 1
1538 if (Math.abs(f) >= 1) {
1544 protected void dampedOverScroll(float amount) {
1545 int screenSize = getViewportWidth();
1547 float f = (amount / screenSize);
1550 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1552 // Clamp this factor, f, to -1 < f < 1
1553 if (Math.abs(f) >= 1) {
1557 int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize);
1559 mOverScrollX = overScrollAmount;
1560 super.scrollTo(mOverScrollX, getScrollY());
1562 mOverScrollX = mMaxScrollX + overScrollAmount;
1563 super.scrollTo(mOverScrollX, getScrollY());
1568 protected void overScroll(float amount) {
1569 dampedOverScroll(amount);
1572 protected float maxOverScroll() {
1573 // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not
1574 // exceed). Used to find out how much extra wallpaper we need for the over scroll effect
1576 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
1577 return OVERSCROLL_DAMP_FACTOR * f;
1580 public void enableFreeScroll() {
1581 setEnableFreeScroll(true);
1584 public void disableFreeScroll() {
1585 setEnableFreeScroll(false);
1588 void updateFreescrollBounds() {
1589 getFreeScrollPageRange(mTempVisiblePagesRange);
1591 mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
1592 mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
1594 mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]);
1595 mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]);
1599 private void setEnableFreeScroll(boolean freeScroll) {
1600 mFreeScroll = freeScroll;
1603 updateFreescrollBounds();
1604 getFreeScrollPageRange(mTempVisiblePagesRange);
1605 if (getCurrentPage() < mTempVisiblePagesRange[0]) {
1606 setCurrentPage(mTempVisiblePagesRange[0]);
1607 } else if (getCurrentPage() > mTempVisiblePagesRange[1]) {
1608 setCurrentPage(mTempVisiblePagesRange[1]);
1612 setEnableOverscroll(!freeScroll);
1615 protected void setEnableOverscroll(boolean enable) {
1616 mAllowOverScroll = enable;
1619 private int getNearestHoverOverPageIndex() {
1620 if (mDragView != null) {
1621 int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2)
1622 + mDragView.getTranslationX());
1623 getFreeScrollPageRange(mTempVisiblePagesRange);
1624 int minDistance = Integer.MAX_VALUE;
1625 int minIndex = indexOfChild(mDragView);
1626 for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) {
1627 View page = getPageAt(i);
1628 int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2);
1629 int d = Math.abs(dragX - pageX);
1630 if (d < minDistance) {
1641 public boolean onTouchEvent(MotionEvent ev) {
1642 if (DISABLE_TOUCH_INTERACTION) {
1646 super.onTouchEvent(ev);
1648 // Skip touch handling if there are no pages to swipe
1649 if (getChildCount() <= 0) return super.onTouchEvent(ev);
1651 acquireVelocityTrackerAndAddMovement(ev);
1653 final int action = ev.getAction();
1655 switch (action & MotionEvent.ACTION_MASK) {
1656 case MotionEvent.ACTION_DOWN:
1658 * If being flinged and user touches, stop the fling. isFinished
1659 * will be false if being flinged.
1661 if (!mScroller.isFinished()) {
1662 abortScrollerAnimation(false);
1665 // Remember where the motion event started
1666 mDownMotionX = mLastMotionX = ev.getX();
1667 mDownMotionY = mLastMotionY = ev.getY();
1668 mDownScrollX = getScrollX();
1669 float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1670 mParentDownMotionX = p[0];
1671 mParentDownMotionY = p[1];
1672 mLastMotionXRemainder = 0;
1674 mActivePointerId = ev.getPointerId(0);
1676 if (mTouchState == TOUCH_STATE_SCROLLING) {
1677 onScrollInteractionBegin();
1682 case MotionEvent.ACTION_MOVE:
1683 if (mTouchState == TOUCH_STATE_SCROLLING) {
1684 // Scroll to follow the motion event
1685 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1687 if (pointerIndex == -1) return true;
1689 final float x = ev.getX(pointerIndex);
1690 final float deltaX = mLastMotionX + mLastMotionXRemainder - x;
1692 mTotalMotionX += Math.abs(deltaX);
1694 // Only scroll and update mLastMotionX if we have moved some discrete amount. We
1695 // keep the remainder because we are actually testing if we've moved from the last
1696 // scrolled position (which is discrete).
1697 if (Math.abs(deltaX) >= 1.0f) {
1699 mSmoothingTime = System.nanoTime() / NANOTIME_DIV;
1700 if (!mDeferScrollUpdate) {
1701 scrollBy((int) deltaX, 0);
1702 if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX);
1707 mLastMotionXRemainder = deltaX - (int) deltaX;
1711 } else if (mTouchState == TOUCH_STATE_REORDERING) {
1712 // Update the last motion position
1713 mLastMotionX = ev.getX();
1714 mLastMotionY = ev.getY();
1716 // Update the parent down so that our zoom animations take this new movement into
1718 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1719 mParentDownMotionX = pt[0];
1720 mParentDownMotionY = pt[1];
1721 updateDragViewTranslationDuringDrag();
1723 // Find the closest page to the touch point
1724 final int dragViewIndex = indexOfChild(mDragView);
1726 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX);
1727 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY);
1728 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX);
1729 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY);
1731 final int pageUnderPointIndex = getNearestHoverOverPageIndex();
1732 if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView)) {
1733 mTempVisiblePagesRange[0] = 0;
1734 mTempVisiblePagesRange[1] = getPageCount() - 1;
1735 getFreeScrollPageRange(mTempVisiblePagesRange);
1736 if (mTempVisiblePagesRange[0] <= pageUnderPointIndex &&
1737 pageUnderPointIndex <= mTempVisiblePagesRange[1] &&
1738 pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) {
1739 mSidePageHoverIndex = pageUnderPointIndex;
1740 mSidePageHoverRunnable = new Runnable() {
1743 // Setup the scroll to the correct page before we swap the views
1744 snapToPage(pageUnderPointIndex);
1746 // For each of the pages between the paged view and the drag view,
1747 // animate them from the previous position to the new position in
1748 // the layout (as a result of the drag view moving in the layout)
1749 int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1;
1750 int lowerIndex = (dragViewIndex < pageUnderPointIndex) ?
1751 dragViewIndex + 1 : pageUnderPointIndex;
1752 int upperIndex = (dragViewIndex > pageUnderPointIndex) ?
1753 dragViewIndex - 1 : pageUnderPointIndex;
1754 for (int i = lowerIndex; i <= upperIndex; ++i) {
1755 View v = getChildAt(i);
1756 // dragViewIndex < pageUnderPointIndex, so after we remove the
1757 // drag view all subsequent views to pageUnderPointIndex will
1759 int oldX = getViewportOffsetX() + getChildOffset(i);
1760 int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta);
1762 // Animate the view translation from its old position to its new
1764 AnimatorSet anim = (AnimatorSet) v.getTag(ANIM_TAG_KEY);
1769 v.setTranslationX(oldX - newX);
1770 anim = new AnimatorSet();
1771 anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION);
1773 ObjectAnimator.ofFloat(v, "translationX", 0f));
1778 removeView(mDragView);
1779 addView(mDragView, pageUnderPointIndex);
1780 mSidePageHoverIndex = -1;
1781 if (mPageIndicator != null) {
1782 mPageIndicator.setActiveMarker(getNextPage());
1786 postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT);
1789 removeCallbacks(mSidePageHoverRunnable);
1790 mSidePageHoverIndex = -1;
1793 determineScrollingStart(ev);
1797 case MotionEvent.ACTION_UP:
1798 if (mTouchState == TOUCH_STATE_SCROLLING) {
1799 final int activePointerId = mActivePointerId;
1800 final int pointerIndex = ev.findPointerIndex(activePointerId);
1801 final float x = ev.getX(pointerIndex);
1802 final VelocityTracker velocityTracker = mVelocityTracker;
1803 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1804 int velocityX = (int) velocityTracker.getXVelocity(activePointerId);
1805 final int deltaX = (int) (x - mDownMotionX);
1806 final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth();
1807 boolean isSignificantMove = Math.abs(deltaX) > pageWidth *
1808 SIGNIFICANT_MOVE_THRESHOLD;
1810 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x);
1812 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING &&
1813 Math.abs(velocityX) > mFlingThresholdVelocity;
1816 // In the case that the page is moved far to one direction and then is flung
1817 // in the opposite direction, we use a threshold to determine whether we should
1818 // just return to the starting page, or if we should skip one further.
1819 boolean returnToOriginalPage = false;
1820 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD &&
1821 Math.signum(velocityX) != Math.signum(deltaX) && isFling) {
1822 returnToOriginalPage = true;
1826 // We give flings precedence over large moves, which is why we short-circuit our
1827 // test for a large move if a fling has been registered. That is, a large
1828 // move to the left and fling to the right will register as a fling to the right.
1829 boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0;
1830 boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0;
1831 if (((isSignificantMove && !isDeltaXLeft && !isFling) ||
1832 (isFling && !isVelocityXLeft)) && mCurrentPage > 0) {
1833 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1;
1834 snapToPageWithVelocity(finalPage, velocityX);
1835 } else if (((isSignificantMove && isDeltaXLeft && !isFling) ||
1836 (isFling && isVelocityXLeft)) &&
1837 mCurrentPage < getChildCount() - 1) {
1838 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1;
1839 snapToPageWithVelocity(finalPage, velocityX);
1841 snapToDestination();
1844 if (!mScroller.isFinished()) {
1845 abortScrollerAnimation(true);
1848 float scaleX = getScaleX();
1849 int vX = (int) (-velocityX * scaleX);
1850 int initialScrollX = (int) (getScrollX() * scaleX);
1852 mScroller.setInterpolator(mDefaultInterpolator);
1853 mScroller.fling(initialScrollX,
1854 getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
1857 onScrollInteractionEnd();
1858 } else if (mTouchState == TOUCH_STATE_PREV_PAGE) {
1859 // at this point we have not moved beyond the touch slop
1860 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1862 int nextPage = Math.max(0, mCurrentPage - 1);
1863 if (nextPage != mCurrentPage) {
1864 snapToPage(nextPage);
1866 snapToDestination();
1868 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) {
1869 // at this point we have not moved beyond the touch slop
1870 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so
1872 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1);
1873 if (nextPage != mCurrentPage) {
1874 snapToPage(nextPage);
1876 snapToDestination();
1878 } else if (mTouchState == TOUCH_STATE_REORDERING) {
1879 // Update the last motion position
1880 mLastMotionX = ev.getX();
1881 mLastMotionY = ev.getY();
1883 // Update the parent down so that our zoom animations take this new movement into
1885 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY);
1886 mParentDownMotionX = pt[0];
1887 mParentDownMotionY = pt[1];
1888 updateDragViewTranslationDuringDrag();
1895 // Remove the callback to wait for the side page hover timeout
1896 removeCallbacks(mSidePageHoverRunnable);
1897 // End any intermediate reordering states
1901 case MotionEvent.ACTION_CANCEL:
1902 if (mTouchState == TOUCH_STATE_SCROLLING) {
1903 snapToDestination();
1908 case MotionEvent.ACTION_POINTER_UP:
1909 onSecondaryPointerUp(ev);
1910 releaseVelocityTracker();
1917 private void resetTouchState() {
1918 releaseVelocityTracker();
1921 mTouchState = TOUCH_STATE_REST;
1922 mActivePointerId = INVALID_POINTER;
1926 * Triggered by scrolling via touch
1928 protected void onScrollInteractionBegin() {
1931 protected void onScrollInteractionEnd() {
1934 protected void onUnhandledTap(MotionEvent ev) {
1935 ((Launcher) getContext()).onClick(this);
1939 public boolean onGenericMotionEvent(MotionEvent event) {
1940 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
1941 switch (event.getAction()) {
1942 case MotionEvent.ACTION_SCROLL: {
1943 // Handle mouse (or ext. device) by shifting the page depending on the scroll
1944 final float vscroll;
1945 final float hscroll;
1946 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
1948 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1950 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
1951 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
1953 if (hscroll != 0 || vscroll != 0) {
1954 boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0)
1955 : (hscroll > 0 || vscroll > 0);
1956 if (isForwardScroll) {
1966 return super.onGenericMotionEvent(event);
1969 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) {
1970 if (mVelocityTracker == null) {
1971 mVelocityTracker = VelocityTracker.obtain();
1973 mVelocityTracker.addMovement(ev);
1976 private void releaseVelocityTracker() {
1977 if (mVelocityTracker != null) {
1978 mVelocityTracker.clear();
1979 mVelocityTracker.recycle();
1980 mVelocityTracker = null;
1984 private void onSecondaryPointerUp(MotionEvent ev) {
1985 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1986 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1987 final int pointerId = ev.getPointerId(pointerIndex);
1988 if (pointerId == mActivePointerId) {
1989 // This was our active pointer going up. Choose a new
1990 // active pointer and adjust accordingly.
1991 // TODO: Make this decision more intelligent.
1992 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1993 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex);
1994 mLastMotionY = ev.getY(newPointerIndex);
1995 mLastMotionXRemainder = 0;
1996 mActivePointerId = ev.getPointerId(newPointerIndex);
1997 if (mVelocityTracker != null) {
1998 mVelocityTracker.clear();
2004 public void requestChildFocus(View child, View focused) {
2005 super.requestChildFocus(child, focused);
2006 int page = indexToPage(indexOfChild(child));
2007 if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
2012 int getPageNearestToCenterOfScreen() {
2013 int minDistanceFromScreenCenter = Integer.MAX_VALUE;
2014 int minDistanceFromScreenCenterIndex = -1;
2015 int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2);
2016 final int childCount = getChildCount();
2017 for (int i = 0; i < childCount; ++i) {
2018 View layout = (View) getPageAt(i);
2019 int childWidth = layout.getMeasuredWidth();
2020 int halfChildWidth = (childWidth / 2);
2021 int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth;
2022 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
2023 if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
2024 minDistanceFromScreenCenter = distanceFromScreenCenter;
2025 minDistanceFromScreenCenterIndex = i;
2028 return minDistanceFromScreenCenterIndex;
2031 protected boolean isInOverScroll() {
2032 return (mOverScrollX > mMaxScrollX || mOverScrollX < 0);
2035 protected int getPageSnapDuration() {
2036 if (isInOverScroll()) {
2037 return OVER_SCROLL_PAGE_SNAP_ANIMATION_DURATION;
2039 return PAGE_SNAP_ANIMATION_DURATION;
2043 protected void snapToDestination() {
2044 snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
2047 private static class ScrollInterpolator implements Interpolator {
2048 public ScrollInterpolator() {
2051 public float getInterpolation(float t) {
2053 return t*t*t*t*t + 1;
2057 // We want the duration of the page snap animation to be influenced by the distance that
2058 // the screen has to travel, however, we don't want this duration to be effected in a
2059 // purely linear fashion. Instead, we use this method to moderate the effect that the distance
2060 // of travel has on the overall snap duration.
2061 float distanceInfluenceForSnapDuration(float f) {
2062 f -= 0.5f; // center the values about 0.
2063 f *= 0.3f * Math.PI / 2.0f;
2064 return (float) Math.sin(f);
2067 protected void snapToPageWithVelocity(int whichPage, int velocity) {
2068 whichPage = validateNewPage(whichPage);
2069 int halfScreenSize = getViewportWidth() / 2;
2071 final int newX = getScrollForPage(whichPage);
2072 int delta = newX - mUnboundedScrollX;
2075 if (Math.abs(velocity) < mMinFlingVelocity || isInOverScroll()) {
2076 // If the velocity is low enough, then treat this more as an automatic page advance
2077 // as opposed to an apparent physical response to flinging
2078 snapToPage(whichPage, getPageSnapDuration());
2082 // Here we compute a "distance" that will be used in the computation of the overall
2083 // snap duration. This is a function of the actual distance that needs to be traveled;
2084 // we keep this value close to half screen size in order to reduce the variance in snap
2085 // duration as a function of the distance the page needs to travel.
2086 float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize));
2087 float distance = halfScreenSize + halfScreenSize *
2088 distanceInfluenceForSnapDuration(distanceRatio);
2090 velocity = Math.abs(velocity);
2091 velocity = Math.max(mMinSnapVelocity, velocity);
2093 // we want the page's snap velocity to approximately match the velocity at which the
2094 // user flings, so we scale the duration by a value near to the derivative of the scroll
2095 // interpolator at zero, ie. 5. We use 4 to make it a little slower.
2096 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
2098 snapToPage(whichPage, delta, duration);
2101 protected void snapToPage(int whichPage) {
2102 snapToPage(whichPage, getPageSnapDuration());
2105 protected void snapToPageImmediately(int whichPage) {
2106 snapToPage(whichPage, getPageSnapDuration(), true, null);
2109 protected void snapToPage(int whichPage, int duration) {
2110 snapToPage(whichPage, duration, false, null);
2113 protected void snapToPage(int whichPage, int duration, TimeInterpolator interpolator) {
2114 snapToPage(whichPage, duration, false, interpolator);
2117 protected void snapToPage(int whichPage, int duration, boolean immediate,
2118 TimeInterpolator interpolator) {
2119 whichPage = validateNewPage(whichPage);
2121 int newX = getScrollForPage(whichPage);
2122 final int sX = mUnboundedScrollX;
2123 final int delta = newX - sX;
2124 snapToPage(whichPage, delta, duration, immediate, interpolator);
2127 protected void snapToPage(int whichPage, int delta, int duration) {
2128 snapToPage(whichPage, delta, duration, false, null);
2131 protected void snapToPage(int whichPage, int delta, int duration, boolean immediate,
2132 TimeInterpolator interpolator) {
2133 whichPage = validateNewPage(whichPage);
2135 mNextPage = whichPage;
2136 View focusedChild = getFocusedChild();
2137 if (focusedChild != null && whichPage != mCurrentPage &&
2138 focusedChild == getPageAt(mCurrentPage)) {
2139 focusedChild.clearFocus();
2143 awakenScrollBars(duration);
2146 } else if (duration == 0) {
2147 duration = Math.abs(delta);
2150 if (!mScroller.isFinished()) {
2151 abortScrollerAnimation(false);
2154 if (interpolator != null) {
2155 mScroller.setInterpolator(interpolator);
2157 mScroller.setInterpolator(mDefaultInterpolator);
2160 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration);
2162 updatePageIndicator();
2164 // Trigger a compute() to finish switching pages if necessary
2169 mForceScreenScrolled = true;
2173 public void scrollLeft() {
2174 if (getNextPage() > 0) snapToPage(getNextPage() - 1);
2177 public void scrollRight() {
2178 if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1);
2181 public int getPageForView(View v) {
2184 ViewParent vp = v.getParent();
2185 int count = getChildCount();
2186 for (int i = 0; i < count; i++) {
2187 if (vp == getPageAt(i)) {
2196 public boolean performLongClick() {
2198 return super.performLongClick();
2201 public static class SavedState extends BaseSavedState {
2202 int currentPage = -1;
2204 SavedState(Parcelable superState) {
2208 @Thunk SavedState(Parcel in) {
2210 currentPage = in.readInt();
2214 public void writeToParcel(Parcel out, int flags) {
2215 super.writeToParcel(out, flags);
2216 out.writeInt(currentPage);
2219 public static final Parcelable.Creator<SavedState> CREATOR =
2220 new Parcelable.Creator<SavedState>() {
2221 public SavedState createFromParcel(Parcel in) {
2222 return new SavedState(in);
2225 public SavedState[] newArray(int size) {
2226 return new SavedState[size];
2231 // Animate the drag view back to the original position
2232 void animateDragViewToOriginalPosition() {
2233 if (mDragView != null) {
2234 AnimatorSet anim = new AnimatorSet();
2235 anim.setDuration(REORDERING_DROP_REPOSITION_DURATION);
2237 ObjectAnimator.ofFloat(mDragView, "translationX", 0f),
2238 ObjectAnimator.ofFloat(mDragView, "translationY", 0f),
2239 ObjectAnimator.ofFloat(mDragView, "scaleX", 1f),
2240 ObjectAnimator.ofFloat(mDragView, "scaleY", 1f));
2241 anim.addListener(new AnimatorListenerAdapter() {
2243 public void onAnimationEnd(Animator animation) {
2244 onPostReorderingAnimationCompleted();
2251 public void onStartReordering() {
2252 // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
2253 mTouchState = TOUCH_STATE_REORDERING;
2254 mIsReordering = true;
2256 // We must invalidate to trigger a redraw to update the layers such that the drag view
2257 // is always drawn on top
2261 @Thunk void onPostReorderingAnimationCompleted() {
2262 // Trigger the callback when reordering has settled
2263 --mPostReorderingPreZoomInRemainingAnimationCount;
2264 if (mPostReorderingPreZoomInRunnable != null &&
2265 mPostReorderingPreZoomInRemainingAnimationCount == 0) {
2266 mPostReorderingPreZoomInRunnable.run();
2267 mPostReorderingPreZoomInRunnable = null;
2271 public void onEndReordering() {
2272 mIsReordering = false;
2275 public boolean startReordering(View v) {
2276 int dragViewIndex = indexOfChild(v);
2278 if (mTouchState != TOUCH_STATE_REST || dragViewIndex == -1) return false;
2280 mTempVisiblePagesRange[0] = 0;
2281 mTempVisiblePagesRange[1] = getPageCount() - 1;
2282 getFreeScrollPageRange(mTempVisiblePagesRange);
2283 mReorderingStarted = true;
2285 // Check if we are within the reordering range
2286 if (mTempVisiblePagesRange[0] <= dragViewIndex &&
2287 dragViewIndex <= mTempVisiblePagesRange[1]) {
2288 // Find the drag view under the pointer
2289 mDragView = getChildAt(dragViewIndex);
2290 mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start();
2291 mDragViewBaselineLeft = mDragView.getLeft();
2292 snapToPage(getPageNearestToCenterOfScreen());
2293 disableFreeScroll();
2294 onStartReordering();
2300 boolean isReordering(boolean testTouchState) {
2301 boolean state = mIsReordering;
2302 if (testTouchState) {
2303 state &= (mTouchState == TOUCH_STATE_REORDERING);
2307 void endReordering() {
2308 // For simplicity, we call endReordering sometimes even if reordering was never started.
2309 // In that case, we don't want to do anything.
2310 if (!mReorderingStarted) return;
2311 mReorderingStarted = false;
2313 // If we haven't flung-to-delete the current child, then we just animate the drag view
2314 // back into position
2315 final Runnable onCompleteRunnable = new Runnable() {
2322 mPostReorderingPreZoomInRunnable = new Runnable() {
2324 onCompleteRunnable.run();
2329 mPostReorderingPreZoomInRemainingAnimationCount =
2330 NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT;
2331 // Snap to the current page
2332 snapToPage(indexOfChild(mDragView), 0);
2333 // Animate the drag view back to the front position
2334 animateDragViewToOriginalPosition();
2337 private static final int ANIM_TAG_KEY = 100;
2341 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
2342 super.onInitializeAccessibilityNodeInfo(info);
2343 info.setScrollable(getPageCount() > 1);
2344 if (getCurrentPage() < getPageCount() - 1) {
2345 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2347 if (getCurrentPage() > 0) {
2348 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2353 public void sendAccessibilityEvent(int eventType) {
2354 // Don't let the view send real scroll events.
2355 if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2356 super.sendAccessibilityEvent(eventType);
2361 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
2362 super.onInitializeAccessibilityEvent(event);
2363 event.setScrollable(true);
2367 public boolean performAccessibilityAction(int action, Bundle arguments) {
2368 if (super.performAccessibilityAction(action, arguments)) {
2372 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2373 if (getCurrentPage() < getPageCount() - 1) {
2378 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2379 if (getCurrentPage() > 0) {
2388 protected String getCurrentPageDescription() {
2389 return String.format(getContext().getString(R.string.default_scroll_format),
2390 getNextPage() + 1, getChildCount());
2394 public boolean onHoverEvent(android.view.MotionEvent event) {