PagedView.java revision c268148f06f5c6c30deb1678e74530ef5aa85a67
1/* 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 17package com.android.launcher3; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.LayoutTransition; 22import android.animation.ObjectAnimator; 23import android.animation.TimeInterpolator; 24import android.annotation.SuppressLint; 25import android.annotation.TargetApi; 26import android.content.Context; 27import android.content.res.TypedArray; 28import android.graphics.Canvas; 29import android.graphics.Matrix; 30import android.graphics.Rect; 31import android.os.Build; 32import android.os.Bundle; 33import android.os.Parcel; 34import android.os.Parcelable; 35import android.util.AttributeSet; 36import android.util.DisplayMetrics; 37import android.util.Log; 38import android.view.InputDevice; 39import android.view.KeyEvent; 40import android.view.MotionEvent; 41import android.view.VelocityTracker; 42import android.view.View; 43import android.view.ViewConfiguration; 44import android.view.ViewGroup; 45import android.view.ViewParent; 46import android.view.accessibility.AccessibilityEvent; 47import android.view.accessibility.AccessibilityManager; 48import android.view.accessibility.AccessibilityNodeInfo; 49import android.view.animation.Interpolator; 50import com.android.launcher3.util.LauncherEdgeEffect; 51import com.android.launcher3.util.Thunk; 52import java.util.ArrayList; 53 54/** 55 * An abstraction of the original Workspace which supports browsing through a 56 * sequential list of "pages" 57 */ 58public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarchyChangeListener { 59 private static final String TAG = "PagedView"; 60 private static final boolean DEBUG = false; 61 protected static final int INVALID_PAGE = -1; 62 63 // the min drag distance for a fling to register, to prevent random page shifts 64 private static final int MIN_LENGTH_FOR_FLING = 25; 65 66 protected static final int PAGE_SNAP_ANIMATION_DURATION = 750; 67 protected static final int SLOW_PAGE_SNAP_ANIMATION_DURATION = 950; 68 69 private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; 70 // The page is moved more than halfway, automatically move to the next page on touch up. 71 private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; 72 73 private static final float MAX_SCROLL_PROGRESS = 1.0f; 74 75 // The following constants need to be scaled based on density. The scaled versions will be 76 // assigned to the corresponding member variables below. 77 private static final int FLING_THRESHOLD_VELOCITY = 500; 78 private static final int MIN_SNAP_VELOCITY = 1500; 79 private static final int MIN_FLING_VELOCITY = 250; 80 81 public static final int INVALID_RESTORE_PAGE = -1001; 82 83 private boolean mFreeScroll = false; 84 private int mFreeScrollMinScrollX = -1; 85 private int mFreeScrollMaxScrollX = -1; 86 87 protected int mFlingThresholdVelocity; 88 protected int mMinFlingVelocity; 89 protected int mMinSnapVelocity; 90 91 protected boolean mFirstLayout = true; 92 private int mNormalChildHeight; 93 94 protected int mCurrentPage; 95 protected int mRestorePage = INVALID_RESTORE_PAGE; 96 private int mChildCountOnLastLayout; 97 98 protected int mNextPage = INVALID_PAGE; 99 private int mMaxScrollX; 100 protected LauncherScroller mScroller; 101 private Interpolator mDefaultInterpolator; 102 private VelocityTracker mVelocityTracker; 103 @Thunk int mPageSpacing = 0; 104 105 private float mParentDownMotionX; 106 private float mParentDownMotionY; 107 private float mDownMotionX; 108 private float mDownMotionY; 109 private float mDownScrollX; 110 private float mDragViewBaselineLeft; 111 private float mLastMotionX; 112 private float mLastMotionXRemainder; 113 private float mLastMotionY; 114 private float mTotalMotionX; 115 private int mLastScreenCenter = -1; 116 117 private boolean mCancelTap; 118 119 private int[] mPageScrolls; 120 121 protected final static int TOUCH_STATE_REST = 0; 122 protected final static int TOUCH_STATE_SCROLLING = 1; 123 protected final static int TOUCH_STATE_PREV_PAGE = 2; 124 protected final static int TOUCH_STATE_NEXT_PAGE = 3; 125 protected final static int TOUCH_STATE_REORDERING = 4; 126 127 protected int mTouchState = TOUCH_STATE_REST; 128 private boolean mForceScreenScrolled = false; 129 130 protected OnLongClickListener mLongClickListener; 131 132 protected int mTouchSlop; 133 private int mMaximumVelocity; 134 protected int mCellCountX = 0; 135 protected int mCellCountY = 0; 136 protected boolean mAllowOverScroll = true; 137 protected int[] mTempVisiblePagesRange = new int[2]; 138 139 protected static final int INVALID_POINTER = -1; 140 141 protected int mActivePointerId = INVALID_POINTER; 142 143 private PageSwitchListener mPageSwitchListener; 144 145 // If true, modify alpha of neighboring pages as user scrolls left/right 146 protected boolean mFadeInAdjacentScreens = false; 147 148 protected boolean mIsPageMoving = false; 149 150 private boolean mWasInOverscroll = false; 151 152 // Page Indicator 153 @Thunk int mPageIndicatorViewId; 154 @Thunk PageIndicator mPageIndicator; 155 // The viewport whether the pages are to be contained (the actual view may be larger than the 156 // viewport) 157 private Rect mViewport = new Rect(); 158 159 // Reordering 160 // We use the min scale to determine how much to expand the actually PagedView measured 161 // dimensions such that when we are zoomed out, the view is not clipped 162 private static int REORDERING_DROP_REPOSITION_DURATION = 200; 163 @Thunk static int REORDERING_REORDER_REPOSITION_DURATION = 300; 164 private static int REORDERING_SIDE_PAGE_HOVER_TIMEOUT = 80; 165 166 private float mMinScale = 1f; 167 private boolean mUseMinScale = false; 168 @Thunk View mDragView; 169 private Runnable mSidePageHoverRunnable; 170 @Thunk int mSidePageHoverIndex = -1; 171 // This variable's scope is only for the duration of startReordering() and endReordering() 172 private boolean mReorderingStarted = false; 173 // This variable's scope is for the duration of startReordering() and after the zoomIn() 174 // animation after endReordering() 175 private boolean mIsReordering; 176 // The runnable that settles the page after snapToPage and animateDragViewToOriginalPosition 177 private static final int NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT = 2; 178 private int mPostReorderingPreZoomInRemainingAnimationCount; 179 private Runnable mPostReorderingPreZoomInRunnable; 180 181 // Convenience/caching 182 private static final Matrix sTmpInvMatrix = new Matrix(); 183 private static final float[] sTmpPoint = new float[2]; 184 private static final int[] sTmpIntPoint = new int[2]; 185 private static final Rect sTmpRect = new Rect(); 186 187 protected final Rect mInsets = new Rect(); 188 protected final boolean mIsRtl; 189 190 // Edge effect 191 private final LauncherEdgeEffect mEdgeGlowLeft = new LauncherEdgeEffect(); 192 private final LauncherEdgeEffect mEdgeGlowRight = new LauncherEdgeEffect(); 193 194 public interface PageSwitchListener { 195 void onPageSwitch(View newPage, int newPageIndex); 196 } 197 198 public PagedView(Context context) { 199 this(context, null); 200 } 201 202 public PagedView(Context context, AttributeSet attrs) { 203 this(context, attrs, 0); 204 } 205 206 public PagedView(Context context, AttributeSet attrs, int defStyle) { 207 super(context, attrs, defStyle); 208 209 TypedArray a = context.obtainStyledAttributes(attrs, 210 R.styleable.PagedView, defStyle, 0); 211 mPageIndicatorViewId = a.getResourceId(R.styleable.PagedView_pageIndicator, -1); 212 a.recycle(); 213 214 setHapticFeedbackEnabled(false); 215 mIsRtl = Utilities.isRtl(getResources()); 216 init(); 217 } 218 219 /** 220 * Initializes various states for this workspace. 221 */ 222 protected void init() { 223 mScroller = new LauncherScroller(getContext()); 224 setDefaultInterpolator(new ScrollInterpolator()); 225 mCurrentPage = 0; 226 227 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 228 mTouchSlop = configuration.getScaledPagingTouchSlop(); 229 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 230 231 float density = getResources().getDisplayMetrics().density; 232 mFlingThresholdVelocity = (int) (FLING_THRESHOLD_VELOCITY * density); 233 mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density); 234 mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density); 235 setOnHierarchyChangeListener(this); 236 setWillNotDraw(false); 237 } 238 239 protected void setEdgeGlowColor(int color) { 240 mEdgeGlowLeft.setColor(color); 241 mEdgeGlowRight.setColor(color); 242 } 243 244 protected void setDefaultInterpolator(Interpolator interpolator) { 245 mDefaultInterpolator = interpolator; 246 mScroller.setInterpolator(mDefaultInterpolator); 247 } 248 249 protected void onAttachedToWindow() { 250 super.onAttachedToWindow(); 251 252 // Hook up the page indicator 253 ViewGroup parent = (ViewGroup) getParent(); 254 ViewGroup grandParent = (ViewGroup) parent.getParent(); 255 if (mPageIndicator == null && mPageIndicatorViewId > -1) { 256 mPageIndicator = (PageIndicator) grandParent.findViewById(mPageIndicatorViewId); 257 mPageIndicator.removeAllMarkers(true); 258 259 ArrayList<PageIndicator.PageMarkerResources> markers = 260 new ArrayList<PageIndicator.PageMarkerResources>(); 261 for (int i = 0; i < getChildCount(); ++i) { 262 markers.add(getPageIndicatorMarker(i)); 263 } 264 265 mPageIndicator.addMarkers(markers, true); 266 267 OnClickListener listener = getPageIndicatorClickListener(); 268 if (listener != null) { 269 mPageIndicator.setOnClickListener(listener); 270 } 271 mPageIndicator.setContentDescription(getPageIndicatorDescription()); 272 } 273 } 274 275 protected String getPageIndicatorDescription() { 276 return getCurrentPageDescription(); 277 } 278 279 protected OnClickListener getPageIndicatorClickListener() { 280 return null; 281 } 282 283 @Override 284 protected void onDetachedFromWindow() { 285 super.onDetachedFromWindow(); 286 // Unhook the page indicator 287 mPageIndicator = null; 288 } 289 290 // Convenience methods to map points from self to parent and vice versa 291 private float[] mapPointFromViewToParent(View v, float x, float y) { 292 sTmpPoint[0] = x; 293 sTmpPoint[1] = y; 294 v.getMatrix().mapPoints(sTmpPoint); 295 sTmpPoint[0] += v.getLeft(); 296 sTmpPoint[1] += v.getTop(); 297 return sTmpPoint; 298 } 299 private float[] mapPointFromParentToView(View v, float x, float y) { 300 sTmpPoint[0] = x - v.getLeft(); 301 sTmpPoint[1] = y - v.getTop(); 302 v.getMatrix().invert(sTmpInvMatrix); 303 sTmpInvMatrix.mapPoints(sTmpPoint); 304 return sTmpPoint; 305 } 306 307 private void updateDragViewTranslationDuringDrag() { 308 if (mDragView != null) { 309 float x = (mLastMotionX - mDownMotionX) + (getScrollX() - mDownScrollX) + 310 (mDragViewBaselineLeft - mDragView.getLeft()); 311 float y = mLastMotionY - mDownMotionY; 312 mDragView.setTranslationX(x); 313 mDragView.setTranslationY(y); 314 315 if (DEBUG) Log.d(TAG, "PagedView.updateDragViewTranslationDuringDrag(): " 316 + x + ", " + y); 317 } 318 } 319 320 public void setMinScale(float f) { 321 mMinScale = f; 322 mUseMinScale = true; 323 requestLayout(); 324 } 325 326 @Override 327 public void setScaleX(float scaleX) { 328 super.setScaleX(scaleX); 329 if (isReordering(true)) { 330 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); 331 mLastMotionX = p[0]; 332 mLastMotionY = p[1]; 333 updateDragViewTranslationDuringDrag(); 334 } 335 } 336 337 // Convenience methods to get the actual width/height of the PagedView (since it is measured 338 // to be larger to account for the minimum possible scale) 339 int getViewportWidth() { 340 return mViewport.width(); 341 } 342 int getViewportHeight() { 343 return mViewport.height(); 344 } 345 346 // Convenience methods to get the offset ASSUMING that we are centering the pages in the 347 // PagedView both horizontally and vertically 348 int getViewportOffsetX() { 349 return (getMeasuredWidth() - getViewportWidth()) / 2; 350 } 351 352 int getViewportOffsetY() { 353 return (getMeasuredHeight() - getViewportHeight()) / 2; 354 } 355 356 PageIndicator getPageIndicator() { 357 return mPageIndicator; 358 } 359 protected PageIndicator.PageMarkerResources getPageIndicatorMarker(int pageIndex) { 360 return new PageIndicator.PageMarkerResources(); 361 } 362 363 /** 364 * Add a page change listener which will be called when a page is _finished_ listening. 365 * 366 */ 367 public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { 368 mPageSwitchListener = pageSwitchListener; 369 if (mPageSwitchListener != null) { 370 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); 371 } 372 } 373 374 /** 375 * Returns the index of the currently displayed page. 376 */ 377 public int getCurrentPage() { 378 return mCurrentPage; 379 } 380 381 /** 382 * Returns the index of page to be shown immediately afterwards. 383 */ 384 int getNextPage() { 385 return (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 386 } 387 388 int getPageCount() { 389 return getChildCount(); 390 } 391 392 public View getPageAt(int index) { 393 return getChildAt(index); 394 } 395 396 protected int indexToPage(int index) { 397 return index; 398 } 399 400 /** 401 * Updates the scroll of the current page immediately to its final scroll position. We use this 402 * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of 403 * the previous tab page. 404 */ 405 protected void updateCurrentPageScroll() { 406 // If the current page is invalid, just reset the scroll position to zero 407 int newX = 0; 408 if (0 <= mCurrentPage && mCurrentPage < getPageCount()) { 409 newX = getScrollForPage(mCurrentPage); 410 } 411 scrollTo(newX, 0); 412 mScroller.setFinalX(newX); 413 forceFinishScroller(); 414 } 415 416 private void abortScrollerAnimation(boolean resetNextPage) { 417 mScroller.abortAnimation(); 418 // We need to clean up the next page here to avoid computeScrollHelper from 419 // updating current page on the pass. 420 if (resetNextPage) { 421 mNextPage = INVALID_PAGE; 422 } 423 } 424 425 private void forceFinishScroller() { 426 mScroller.forceFinished(true); 427 // We need to clean up the next page here to avoid computeScrollHelper from 428 // updating current page on the pass. 429 mNextPage = INVALID_PAGE; 430 } 431 432 private int validateNewPage(int newPage) { 433 int validatedPage = newPage; 434 // When in free scroll mode, we need to clamp to the free scroll page range. 435 if (mFreeScroll) { 436 getFreeScrollPageRange(mTempVisiblePagesRange); 437 validatedPage = Math.max(mTempVisiblePagesRange[0], 438 Math.min(newPage, mTempVisiblePagesRange[1])); 439 } 440 // Ensure that it is clamped by the actual set of children in all cases 441 validatedPage = Math.max(0, Math.min(validatedPage, getPageCount() - 1)); 442 return validatedPage; 443 } 444 445 /** 446 * Sets the current page. 447 */ 448 public void setCurrentPage(int currentPage) { 449 if (!mScroller.isFinished()) { 450 abortScrollerAnimation(true); 451 } 452 // don't introduce any checks like mCurrentPage == currentPage here-- if we change the 453 // the default 454 if (getChildCount() == 0) { 455 return; 456 } 457 mForceScreenScrolled = true; 458 mCurrentPage = validateNewPage(currentPage); 459 updateCurrentPageScroll(); 460 notifyPageSwitchListener(); 461 invalidate(); 462 } 463 464 /** 465 * The restore page will be set in place of the current page at the next (likely first) 466 * layout. 467 */ 468 void setRestorePage(int restorePage) { 469 mRestorePage = restorePage; 470 } 471 int getRestorePage() { 472 return mRestorePage; 473 } 474 475 /** 476 * Should be called whenever the page changes. In the case of a scroll, we wait until the page 477 * has settled. 478 */ 479 protected void notifyPageSwitchListener() { 480 if (mPageSwitchListener != null) { 481 mPageSwitchListener.onPageSwitch(getPageAt(getNextPage()), getNextPage()); 482 } 483 484 updatePageIndicator(); 485 } 486 487 private void updatePageIndicator() { 488 // Update the page indicator (when we aren't reordering) 489 if (mPageIndicator != null) { 490 mPageIndicator.setContentDescription(getPageIndicatorDescription()); 491 if (!isReordering(false)) { 492 mPageIndicator.setActiveMarker(getNextPage()); 493 } 494 } 495 } 496 protected void pageBeginMoving() { 497 if (!mIsPageMoving) { 498 mIsPageMoving = true; 499 onPageBeginMoving(); 500 } 501 } 502 503 protected void pageEndMoving() { 504 if (mIsPageMoving) { 505 mIsPageMoving = false; 506 onPageEndMoving(); 507 } 508 } 509 510 protected boolean isPageMoving() { 511 return mIsPageMoving; 512 } 513 514 // a method that subclasses can override to add behavior 515 protected void onPageBeginMoving() { 516 } 517 518 // a method that subclasses can override to add behavior 519 protected void onPageEndMoving() { 520 mWasInOverscroll = false; 521 } 522 523 /** 524 * Registers the specified listener on each page contained in this workspace. 525 * 526 * @param l The listener used to respond to long clicks. 527 */ 528 @Override 529 public void setOnLongClickListener(OnLongClickListener l) { 530 mLongClickListener = l; 531 final int count = getPageCount(); 532 for (int i = 0; i < count; i++) { 533 getPageAt(i).setOnLongClickListener(l); 534 } 535 super.setOnLongClickListener(l); 536 } 537 538 @Override 539 public void scrollBy(int x, int y) { 540 scrollTo(getScrollX() + x, getScrollY() + y); 541 } 542 543 @Override 544 public void scrollTo(int x, int y) { 545 // In free scroll mode, we clamp the scrollX 546 if (mFreeScroll) { 547 // If the scroller is trying to move to a location beyond the maximum allowed 548 // in the free scroll mode, we make sure to end the scroll operation. 549 if (!mScroller.isFinished() && 550 (x > mFreeScrollMaxScrollX || x < mFreeScrollMinScrollX)) { 551 forceFinishScroller(); 552 } 553 554 x = Math.min(x, mFreeScrollMaxScrollX); 555 x = Math.max(x, mFreeScrollMinScrollX); 556 } 557 558 boolean isXBeforeFirstPage = mIsRtl ? (x > mMaxScrollX) : (x < 0); 559 boolean isXAfterLastPage = mIsRtl ? (x < 0) : (x > mMaxScrollX); 560 if (isXBeforeFirstPage) { 561 super.scrollTo(mIsRtl ? mMaxScrollX : 0, y); 562 if (mAllowOverScroll) { 563 mWasInOverscroll = true; 564 if (mIsRtl) { 565 overScroll(x - mMaxScrollX); 566 } else { 567 overScroll(x); 568 } 569 } 570 } else if (isXAfterLastPage) { 571 super.scrollTo(mIsRtl ? 0 : mMaxScrollX, y); 572 if (mAllowOverScroll) { 573 mWasInOverscroll = true; 574 if (mIsRtl) { 575 overScroll(x); 576 } else { 577 overScroll(x - mMaxScrollX); 578 } 579 } 580 } else { 581 if (mWasInOverscroll) { 582 overScroll(0); 583 mWasInOverscroll = false; 584 } 585 super.scrollTo(x, y); 586 } 587 588 // Update the last motion events when scrolling 589 if (isReordering(true)) { 590 float[] p = mapPointFromParentToView(this, mParentDownMotionX, mParentDownMotionY); 591 mLastMotionX = p[0]; 592 mLastMotionY = p[1]; 593 updateDragViewTranslationDuringDrag(); 594 } 595 } 596 597 private void sendScrollAccessibilityEvent() { 598 AccessibilityManager am = 599 (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 600 if (am.isEnabled()) { 601 if (mCurrentPage != getNextPage()) { 602 AccessibilityEvent ev = 603 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); 604 ev.setScrollable(true); 605 ev.setScrollX(getScrollX()); 606 ev.setScrollY(getScrollY()); 607 ev.setMaxScrollX(mMaxScrollX); 608 ev.setMaxScrollY(0); 609 610 sendAccessibilityEventUnchecked(ev); 611 } 612 } 613 } 614 615 // we moved this functionality to a helper function so SmoothPagedView can reuse it 616 protected boolean computeScrollHelper() { 617 if (mScroller.computeScrollOffset()) { 618 // Don't bother scrolling if the page does not need to be moved 619 if (getScrollX() != mScroller.getCurrX() 620 || getScrollY() != mScroller.getCurrY()) { 621 float scaleX = mFreeScroll ? getScaleX() : 1f; 622 int scrollX = (int) (mScroller.getCurrX() * (1 / scaleX)); 623 scrollTo(scrollX, mScroller.getCurrY()); 624 } 625 invalidate(); 626 return true; 627 } else if (mNextPage != INVALID_PAGE) { 628 sendScrollAccessibilityEvent(); 629 630 mCurrentPage = validateNewPage(mNextPage); 631 mNextPage = INVALID_PAGE; 632 notifyPageSwitchListener(); 633 634 // We don't want to trigger a page end moving unless the page has settled 635 // and the user has stopped scrolling 636 if (mTouchState == TOUCH_STATE_REST) { 637 pageEndMoving(); 638 } 639 640 onPostReorderingAnimationCompleted(); 641 AccessibilityManager am = (AccessibilityManager) 642 getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); 643 if (am.isEnabled()) { 644 // Notify the user when the page changes 645 announceForAccessibility(getCurrentPageDescription()); 646 } 647 return true; 648 } 649 return false; 650 } 651 652 @Override 653 public void computeScroll() { 654 computeScrollHelper(); 655 } 656 657 public static class LayoutParams extends ViewGroup.LayoutParams { 658 public boolean isFullScreenPage = false; 659 660 /** 661 * {@inheritDoc} 662 */ 663 public LayoutParams(int width, int height) { 664 super(width, height); 665 } 666 667 public LayoutParams(Context context, AttributeSet attrs) { 668 super(context, attrs); 669 } 670 671 public LayoutParams(ViewGroup.LayoutParams source) { 672 super(source); 673 } 674 } 675 676 @Override 677 public LayoutParams generateLayoutParams(AttributeSet attrs) { 678 return new LayoutParams(getContext(), attrs); 679 } 680 681 @Override 682 protected LayoutParams generateDefaultLayoutParams() { 683 return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 684 } 685 686 @Override 687 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 688 return new LayoutParams(p); 689 } 690 691 @Override 692 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 693 return p instanceof LayoutParams; 694 } 695 696 public void addFullScreenPage(View page) { 697 LayoutParams lp = generateDefaultLayoutParams(); 698 lp.isFullScreenPage = true; 699 super.addView(page, 0, lp); 700 } 701 702 public int getNormalChildHeight() { 703 return mNormalChildHeight; 704 } 705 706 @Override 707 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 708 if (getChildCount() == 0) { 709 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 710 return; 711 } 712 713 // We measure the dimensions of the PagedView to be larger than the pages so that when we 714 // zoom out (and scale down), the view is still contained in the parent 715 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 716 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 717 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 718 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 719 // NOTE: We multiply by 2f to account for the fact that depending on the offset of the 720 // viewport, we can be at most one and a half screens offset once we scale down 721 DisplayMetrics dm = getResources().getDisplayMetrics(); 722 int maxSize = Math.max(dm.widthPixels + mInsets.left + mInsets.right, 723 dm.heightPixels + mInsets.top + mInsets.bottom); 724 725 int parentWidthSize = (int) (2f * maxSize); 726 int parentHeightSize = (int) (2f * maxSize); 727 int scaledWidthSize, scaledHeightSize; 728 if (mUseMinScale) { 729 scaledWidthSize = (int) (parentWidthSize / mMinScale); 730 scaledHeightSize = (int) (parentHeightSize / mMinScale); 731 } else { 732 scaledWidthSize = widthSize; 733 scaledHeightSize = heightSize; 734 } 735 mViewport.set(0, 0, widthSize, heightSize); 736 737 if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED) { 738 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 739 return; 740 } 741 742 // Return early if we aren't given a proper dimension 743 if (widthSize <= 0 || heightSize <= 0) { 744 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 745 return; 746 } 747 748 /* Allow the height to be set as WRAP_CONTENT. This allows the particular case 749 * of the All apps view on XLarge displays to not take up more space then it needs. Width 750 * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect 751 * each page to have the same width. 752 */ 753 final int verticalPadding = getPaddingTop() + getPaddingBottom(); 754 final int horizontalPadding = getPaddingLeft() + getPaddingRight(); 755 756 int referenceChildWidth = 0; 757 // The children are given the same width and height as the workspace 758 // unless they were set to WRAP_CONTENT 759 if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize); 760 if (DEBUG) Log.d(TAG, "PagedView.scaledSize: " + scaledWidthSize + ", " + scaledHeightSize); 761 if (DEBUG) Log.d(TAG, "PagedView.parentSize: " + parentWidthSize + ", " + parentHeightSize); 762 if (DEBUG) Log.d(TAG, "PagedView.horizontalPadding: " + horizontalPadding); 763 if (DEBUG) Log.d(TAG, "PagedView.verticalPadding: " + verticalPadding); 764 final int childCount = getChildCount(); 765 for (int i = 0; i < childCount; i++) { 766 // disallowing padding in paged view (just pass 0) 767 final View child = getPageAt(i); 768 if (child.getVisibility() != GONE) { 769 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 770 771 int childWidthMode; 772 int childHeightMode; 773 int childWidth; 774 int childHeight; 775 776 if (!lp.isFullScreenPage) { 777 if (lp.width == LayoutParams.WRAP_CONTENT) { 778 childWidthMode = MeasureSpec.AT_MOST; 779 } else { 780 childWidthMode = MeasureSpec.EXACTLY; 781 } 782 783 if (lp.height == LayoutParams.WRAP_CONTENT) { 784 childHeightMode = MeasureSpec.AT_MOST; 785 } else { 786 childHeightMode = MeasureSpec.EXACTLY; 787 } 788 789 childWidth = getViewportWidth() - horizontalPadding 790 - mInsets.left - mInsets.right; 791 childHeight = getViewportHeight() - verticalPadding 792 - mInsets.top - mInsets.bottom; 793 mNormalChildHeight = childHeight; 794 } else { 795 childWidthMode = MeasureSpec.EXACTLY; 796 childHeightMode = MeasureSpec.EXACTLY; 797 798 childWidth = getViewportWidth() - mInsets.left - mInsets.right; 799 childHeight = getViewportHeight(); 800 } 801 if (referenceChildWidth == 0) { 802 referenceChildWidth = childWidth; 803 } 804 805 final int childWidthMeasureSpec = 806 MeasureSpec.makeMeasureSpec(childWidth, childWidthMode); 807 final int childHeightMeasureSpec = 808 MeasureSpec.makeMeasureSpec(childHeight, childHeightMode); 809 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 810 } 811 } 812 setMeasuredDimension(scaledWidthSize, scaledHeightSize); 813 } 814 815 @SuppressLint("DrawAllocation") 816 @Override 817 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 818 if (getChildCount() == 0) { 819 return; 820 } 821 822 if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); 823 final int childCount = getChildCount(); 824 825 int offsetX = getViewportOffsetX(); 826 int offsetY = getViewportOffsetY(); 827 828 // Update the viewport offsets 829 mViewport.offset(offsetX, offsetY); 830 831 final int startIndex = mIsRtl ? childCount - 1 : 0; 832 final int endIndex = mIsRtl ? -1 : childCount; 833 final int delta = mIsRtl ? -1 : 1; 834 835 int verticalPadding = getPaddingTop() + getPaddingBottom(); 836 837 LayoutParams lp = (LayoutParams) getChildAt(startIndex).getLayoutParams(); 838 LayoutParams nextLp; 839 840 int childLeft = offsetX + (lp.isFullScreenPage ? 0 : getPaddingLeft()); 841 if (mPageScrolls == null || childCount != mChildCountOnLastLayout) { 842 mPageScrolls = new int[childCount]; 843 } 844 845 for (int i = startIndex; i != endIndex; i += delta) { 846 final View child = getPageAt(i); 847 if (child.getVisibility() != View.GONE) { 848 lp = (LayoutParams) child.getLayoutParams(); 849 int childTop; 850 if (lp.isFullScreenPage) { 851 childTop = offsetY; 852 } else { 853 childTop = offsetY + getPaddingTop() + mInsets.top; 854 childTop += (getViewportHeight() - mInsets.top - mInsets.bottom - verticalPadding - child.getMeasuredHeight()) / 2; 855 } 856 857 final int childWidth = child.getMeasuredWidth(); 858 final int childHeight = child.getMeasuredHeight(); 859 860 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop); 861 child.layout(childLeft, childTop, 862 childLeft + child.getMeasuredWidth(), childTop + childHeight); 863 864 int scrollOffsetLeft = lp.isFullScreenPage ? 0 : getPaddingLeft(); 865 mPageScrolls[i] = childLeft - scrollOffsetLeft - offsetX; 866 867 int pageGap = mPageSpacing; 868 int next = i + delta; 869 if (next != endIndex) { 870 nextLp = (LayoutParams) getPageAt(next).getLayoutParams(); 871 } else { 872 nextLp = null; 873 } 874 875 // Prevent full screen pages from showing in the viewport 876 // when they are not the current page. 877 if (lp.isFullScreenPage) { 878 pageGap = getPaddingLeft(); 879 } else if (nextLp != null && nextLp.isFullScreenPage) { 880 pageGap = getPaddingRight(); 881 } 882 883 childLeft += childWidth + pageGap + getChildGap(); 884 } 885 } 886 887 final LayoutTransition transition = getLayoutTransition(); 888 // If the transition is running defer updating max scroll, as some empty pages could 889 // still be present, and a max scroll change could cause sudden jumps in scroll. 890 if (transition != null && transition.isRunning()) { 891 transition.addTransitionListener(new LayoutTransition.TransitionListener() { 892 893 @Override 894 public void startTransition(LayoutTransition transition, ViewGroup container, 895 View view, int transitionType) { } 896 897 @Override 898 public void endTransition(LayoutTransition transition, ViewGroup container, 899 View view, int transitionType) { 900 // Wait until all transitions are complete. 901 if (!transition.isRunning()) { 902 transition.removeTransitionListener(this); 903 updateMaxScrollX(); 904 } 905 } 906 }); 907 } else { 908 updateMaxScrollX(); 909 } 910 911 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < childCount) { 912 updateCurrentPageScroll(); 913 mFirstLayout = false; 914 } 915 916 if (mScroller.isFinished() && mChildCountOnLastLayout != childCount) { 917 if (mRestorePage != INVALID_RESTORE_PAGE) { 918 setCurrentPage(mRestorePage); 919 mRestorePage = INVALID_RESTORE_PAGE; 920 } else { 921 setCurrentPage(getNextPage()); 922 } 923 } 924 mChildCountOnLastLayout = childCount; 925 926 if (isReordering(true)) { 927 updateDragViewTranslationDuringDrag(); 928 } 929 } 930 931 protected int getChildGap() { 932 return 0; 933 } 934 935 @Thunk void updateMaxScrollX() { 936 int childCount = getChildCount(); 937 if (childCount > 0) { 938 final int index = mIsRtl ? 0 : childCount - 1; 939 mMaxScrollX = getScrollForPage(index); 940 } else { 941 mMaxScrollX = 0; 942 } 943 } 944 945 public void setPageSpacing(int pageSpacing) { 946 mPageSpacing = pageSpacing; 947 requestLayout(); 948 } 949 950 /** 951 * Called when the center screen changes during scrolling. 952 */ 953 protected void screenScrolled(int screenCenter) { } 954 955 @Override 956 public void onChildViewAdded(View parent, View child) { 957 // Update the page indicator, we don't update the page indicator as we 958 // add/remove pages 959 if (mPageIndicator != null && !isReordering(false)) { 960 int pageIndex = indexOfChild(child); 961 mPageIndicator.addMarker(pageIndex, 962 getPageIndicatorMarker(pageIndex), 963 true); 964 } 965 966 // This ensures that when children are added, they get the correct transforms / alphas 967 // in accordance with any scroll effects. 968 mForceScreenScrolled = true; 969 updateFreescrollBounds(); 970 invalidate(); 971 } 972 973 @Override 974 public void onChildViewRemoved(View parent, View child) { 975 mForceScreenScrolled = true; 976 updateFreescrollBounds(); 977 invalidate(); 978 } 979 980 private void removeMarkerForView(int index) { 981 // Update the page indicator, we don't update the page indicator as we 982 // add/remove pages 983 if (mPageIndicator != null && !isReordering(false)) { 984 mPageIndicator.removeMarker(index, true); 985 } 986 } 987 988 @Override 989 public void removeView(View v) { 990 // XXX: We should find a better way to hook into this before the view 991 // gets removed form its parent... 992 removeMarkerForView(indexOfChild(v)); 993 super.removeView(v); 994 } 995 @Override 996 public void removeViewInLayout(View v) { 997 // XXX: We should find a better way to hook into this before the view 998 // gets removed form its parent... 999 removeMarkerForView(indexOfChild(v)); 1000 super.removeViewInLayout(v); 1001 } 1002 @Override 1003 public void removeViewAt(int index) { 1004 // XXX: We should find a better way to hook into this before the view 1005 // gets removed form its parent... 1006 removeMarkerForView(index); 1007 super.removeViewAt(index); 1008 } 1009 @Override 1010 public void removeAllViewsInLayout() { 1011 // Update the page indicator, we don't update the page indicator as we 1012 // add/remove pages 1013 if (mPageIndicator != null) { 1014 mPageIndicator.removeAllMarkers(true); 1015 } 1016 1017 super.removeAllViewsInLayout(); 1018 } 1019 1020 protected int getChildOffset(int index) { 1021 if (index < 0 || index > getChildCount() - 1) return 0; 1022 1023 int offset = getPageAt(index).getLeft() - getViewportOffsetX(); 1024 1025 return offset; 1026 } 1027 1028 protected void getFreeScrollPageRange(int[] range) { 1029 range[0] = 0; 1030 range[1] = Math.max(0, getChildCount() - 1); 1031 } 1032 1033 protected void getVisiblePages(int[] range) { 1034 final int pageCount = getChildCount(); 1035 sTmpIntPoint[0] = sTmpIntPoint[1] = 0; 1036 1037 range[0] = -1; 1038 range[1] = -1; 1039 1040 if (pageCount > 0) { 1041 int viewportWidth = getViewportWidth(); 1042 int curScreen = 0; 1043 1044 int count = getChildCount(); 1045 for (int i = 0; i < count; i++) { 1046 View currPage = getPageAt(i); 1047 1048 sTmpIntPoint[0] = 0; 1049 Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false); 1050 if (sTmpIntPoint[0] > viewportWidth) { 1051 if (range[0] == -1) { 1052 continue; 1053 } else { 1054 break; 1055 } 1056 } 1057 1058 sTmpIntPoint[0] = currPage.getMeasuredWidth(); 1059 Utilities.getDescendantCoordRelativeToParent(currPage, this, sTmpIntPoint, false); 1060 if (sTmpIntPoint[0] < 0) { 1061 if (range[0] == -1) { 1062 continue; 1063 } else { 1064 break; 1065 } 1066 } 1067 curScreen = i; 1068 if (range[0] < 0) { 1069 range[0] = curScreen; 1070 } 1071 } 1072 1073 range[1] = curScreen; 1074 } else { 1075 range[0] = -1; 1076 range[1] = -1; 1077 } 1078 } 1079 1080 protected boolean shouldDrawChild(View child) { 1081 return child.getVisibility() == VISIBLE; 1082 } 1083 1084 @Override 1085 protected void dispatchDraw(Canvas canvas) { 1086 // Find out which screens are visible; as an optimization we only call draw on them 1087 final int pageCount = getChildCount(); 1088 if (pageCount > 0) { 1089 int halfScreenSize = getViewportWidth() / 2; 1090 int screenCenter = getScrollX() + halfScreenSize; 1091 1092 if (screenCenter != mLastScreenCenter || mForceScreenScrolled) { 1093 // set mForceScreenScrolled before calling screenScrolled so that screenScrolled can 1094 // set it for the next frame 1095 mForceScreenScrolled = false; 1096 screenScrolled(screenCenter); 1097 mLastScreenCenter = screenCenter; 1098 } 1099 1100 getVisiblePages(mTempVisiblePagesRange); 1101 final int leftScreen = mTempVisiblePagesRange[0]; 1102 final int rightScreen = mTempVisiblePagesRange[1]; 1103 if (leftScreen != -1 && rightScreen != -1) { 1104 final long drawingTime = getDrawingTime(); 1105 // Clip to the bounds 1106 canvas.save(); 1107 canvas.clipRect(getScrollX(), getScrollY(), getScrollX() + getRight() - getLeft(), 1108 getScrollY() + getBottom() - getTop()); 1109 1110 // Draw all the children, leaving the drag view for last 1111 for (int i = pageCount - 1; i >= 0; i--) { 1112 final View v = getPageAt(i); 1113 if (v == mDragView) continue; 1114 if (leftScreen <= i && i <= rightScreen && shouldDrawChild(v)) { 1115 drawChild(canvas, v, drawingTime); 1116 } 1117 } 1118 // Draw the drag view on top (if there is one) 1119 if (mDragView != null) { 1120 drawChild(canvas, mDragView, drawingTime); 1121 } 1122 1123 canvas.restore(); 1124 } 1125 } 1126 } 1127 1128 @Override 1129 public void draw(Canvas canvas) { 1130 super.draw(canvas); 1131 if (getPageCount() > 0) { 1132 if (!mEdgeGlowLeft.isFinished()) { 1133 final int restoreCount = canvas.save(); 1134 Rect display = mViewport; 1135 canvas.translate(display.left, display.top); 1136 canvas.rotate(270); 1137 1138 getEdgeVerticalPostion(sTmpIntPoint); 1139 canvas.translate(display.top - sTmpIntPoint[1], 0); 1140 mEdgeGlowLeft.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width()); 1141 if (mEdgeGlowLeft.draw(canvas)) { 1142 postInvalidateOnAnimation(); 1143 } 1144 canvas.restoreToCount(restoreCount); 1145 } 1146 if (!mEdgeGlowRight.isFinished()) { 1147 final int restoreCount = canvas.save(); 1148 Rect display = mViewport; 1149 canvas.translate(display.left + mPageScrolls[mIsRtl ? 0 : (getPageCount() - 1)], display.top); 1150 canvas.rotate(90); 1151 1152 getEdgeVerticalPostion(sTmpIntPoint); 1153 canvas.translate(sTmpIntPoint[0] - display.top, -display.width()); 1154 mEdgeGlowRight.setSize(sTmpIntPoint[1] - sTmpIntPoint[0], display.width()); 1155 if (mEdgeGlowRight.draw(canvas)) { 1156 postInvalidateOnAnimation(); 1157 } 1158 canvas.restoreToCount(restoreCount); 1159 } 1160 } 1161 } 1162 1163 /** 1164 * Returns the top and bottom position for the edge effect. 1165 */ 1166 protected abstract void getEdgeVerticalPostion(int[] pos); 1167 1168 @Override 1169 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 1170 int page = indexToPage(indexOfChild(child)); 1171 if (page != mCurrentPage || !mScroller.isFinished()) { 1172 snapToPage(page); 1173 return true; 1174 } 1175 return false; 1176 } 1177 1178 @Override 1179 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 1180 int focusablePage; 1181 if (mNextPage != INVALID_PAGE) { 1182 focusablePage = mNextPage; 1183 } else { 1184 focusablePage = mCurrentPage; 1185 } 1186 View v = getPageAt(focusablePage); 1187 if (v != null) { 1188 return v.requestFocus(direction, previouslyFocusedRect); 1189 } 1190 return false; 1191 } 1192 1193 @Override 1194 public boolean dispatchUnhandledMove(View focused, int direction) { 1195 // XXX-RTL: This will be fixed in a future CL 1196 if (direction == View.FOCUS_LEFT) { 1197 if (getCurrentPage() > 0) { 1198 snapToPage(getCurrentPage() - 1); 1199 return true; 1200 } 1201 } else if (direction == View.FOCUS_RIGHT) { 1202 if (getCurrentPage() < getPageCount() - 1) { 1203 snapToPage(getCurrentPage() + 1); 1204 return true; 1205 } 1206 } 1207 return super.dispatchUnhandledMove(focused, direction); 1208 } 1209 1210 @Override 1211 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1212 // XXX-RTL: This will be fixed in a future CL 1213 if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { 1214 getPageAt(mCurrentPage).addFocusables(views, direction, focusableMode); 1215 } 1216 if (direction == View.FOCUS_LEFT) { 1217 if (mCurrentPage > 0) { 1218 getPageAt(mCurrentPage - 1).addFocusables(views, direction, focusableMode); 1219 } 1220 } else if (direction == View.FOCUS_RIGHT){ 1221 if (mCurrentPage < getPageCount() - 1) { 1222 getPageAt(mCurrentPage + 1).addFocusables(views, direction, focusableMode); 1223 } 1224 } 1225 } 1226 1227 /** 1228 * If one of our descendant views decides that it could be focused now, only 1229 * pass that along if it's on the current page. 1230 * 1231 * This happens when live folders requery, and if they're off page, they 1232 * end up calling requestFocus, which pulls it on page. 1233 */ 1234 @Override 1235 public void focusableViewAvailable(View focused) { 1236 View current = getPageAt(mCurrentPage); 1237 View v = focused; 1238 while (true) { 1239 if (v == current) { 1240 super.focusableViewAvailable(focused); 1241 return; 1242 } 1243 if (v == this) { 1244 return; 1245 } 1246 ViewParent parent = v.getParent(); 1247 if (parent instanceof View) { 1248 v = (View)v.getParent(); 1249 } else { 1250 return; 1251 } 1252 } 1253 } 1254 1255 /** 1256 * {@inheritDoc} 1257 */ 1258 @Override 1259 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 1260 if (disallowIntercept) { 1261 // We need to make sure to cancel our long press if 1262 // a scrollable widget takes over touch events 1263 final View currentPage = getPageAt(mCurrentPage); 1264 currentPage.cancelLongPress(); 1265 } 1266 super.requestDisallowInterceptTouchEvent(disallowIntercept); 1267 } 1268 1269 /** 1270 * Return true if a tap at (x, y) should trigger a flip to the previous page. 1271 */ 1272 protected boolean hitsPreviousPage(float x, float y) { 1273 if (mIsRtl) { 1274 return (x > (getViewportOffsetX() + getViewportWidth() - 1275 getPaddingRight() - mPageSpacing)); 1276 } 1277 return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing); 1278 } 1279 1280 /** 1281 * Return true if a tap at (x, y) should trigger a flip to the next page. 1282 */ 1283 protected boolean hitsNextPage(float x, float y) { 1284 if (mIsRtl) { 1285 return (x < getViewportOffsetX() + getPaddingLeft() + mPageSpacing); 1286 } 1287 return (x > (getViewportOffsetX() + getViewportWidth() - 1288 getPaddingRight() - mPageSpacing)); 1289 } 1290 1291 /** Returns whether x and y originated within the buffered viewport */ 1292 private boolean isTouchPointInViewportWithBuffer(int x, int y) { 1293 sTmpRect.set(mViewport.left - mViewport.width() / 2, mViewport.top, 1294 mViewport.right + mViewport.width() / 2, mViewport.bottom); 1295 return sTmpRect.contains(x, y); 1296 } 1297 1298 @Override 1299 public boolean onInterceptTouchEvent(MotionEvent ev) { 1300 /* 1301 * This method JUST determines whether we want to intercept the motion. 1302 * If we return true, onTouchEvent will be called and we do the actual 1303 * scrolling there. 1304 */ 1305 acquireVelocityTrackerAndAddMovement(ev); 1306 1307 // Skip touch handling if there are no pages to swipe 1308 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); 1309 1310 /* 1311 * Shortcut the most recurring case: the user is in the dragging 1312 * state and he is moving his finger. We want to intercept this 1313 * motion. 1314 */ 1315 final int action = ev.getAction(); 1316 if ((action == MotionEvent.ACTION_MOVE) && 1317 (mTouchState == TOUCH_STATE_SCROLLING)) { 1318 return true; 1319 } 1320 1321 switch (action & MotionEvent.ACTION_MASK) { 1322 case MotionEvent.ACTION_MOVE: { 1323 /* 1324 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1325 * whether the user has moved far enough from his original down touch. 1326 */ 1327 if (mActivePointerId != INVALID_POINTER) { 1328 determineScrollingStart(ev); 1329 } 1330 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN 1331 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN 1332 // i.e. fall through to the next case (don't break) 1333 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events 1334 // while it's small- this was causing a crash before we checked for INVALID_POINTER) 1335 break; 1336 } 1337 1338 case MotionEvent.ACTION_DOWN: { 1339 final float x = ev.getX(); 1340 final float y = ev.getY(); 1341 // Remember location of down touch 1342 mDownMotionX = x; 1343 mDownMotionY = y; 1344 mDownScrollX = getScrollX(); 1345 mLastMotionX = x; 1346 mLastMotionY = y; 1347 float[] p = mapPointFromViewToParent(this, x, y); 1348 mParentDownMotionX = p[0]; 1349 mParentDownMotionY = p[1]; 1350 mLastMotionXRemainder = 0; 1351 mTotalMotionX = 0; 1352 mActivePointerId = ev.getPointerId(0); 1353 1354 /* 1355 * If being flinged and user touches the screen, initiate drag; 1356 * otherwise don't. mScroller.isFinished should be false when 1357 * being flinged. 1358 */ 1359 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); 1360 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop / 3); 1361 1362 if (finishedScrolling) { 1363 mTouchState = TOUCH_STATE_REST; 1364 if (!mScroller.isFinished() && !mFreeScroll) { 1365 setCurrentPage(getNextPage()); 1366 pageEndMoving(); 1367 } 1368 } else { 1369 if (isTouchPointInViewportWithBuffer((int) mDownMotionX, (int) mDownMotionY)) { 1370 mTouchState = TOUCH_STATE_SCROLLING; 1371 } else { 1372 mTouchState = TOUCH_STATE_REST; 1373 } 1374 } 1375 1376 break; 1377 } 1378 1379 case MotionEvent.ACTION_UP: 1380 case MotionEvent.ACTION_CANCEL: 1381 resetTouchState(); 1382 break; 1383 1384 case MotionEvent.ACTION_POINTER_UP: 1385 onSecondaryPointerUp(ev); 1386 releaseVelocityTracker(); 1387 break; 1388 } 1389 1390 /* 1391 * The only time we want to intercept motion events is if we are in the 1392 * drag mode. 1393 */ 1394 return mTouchState != TOUCH_STATE_REST; 1395 } 1396 1397 protected void determineScrollingStart(MotionEvent ev) { 1398 determineScrollingStart(ev, 1.0f); 1399 } 1400 1401 /* 1402 * Determines if we should change the touch state to start scrolling after the 1403 * user moves their touch point too far. 1404 */ 1405 protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { 1406 // Disallow scrolling if we don't have a valid pointer index 1407 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 1408 if (pointerIndex == -1) return; 1409 1410 // Disallow scrolling if we started the gesture from outside the viewport 1411 final float x = ev.getX(pointerIndex); 1412 final float y = ev.getY(pointerIndex); 1413 if (!isTouchPointInViewportWithBuffer((int) x, (int) y)) return; 1414 1415 final int xDiff = (int) Math.abs(x - mLastMotionX); 1416 1417 final int touchSlop = Math.round(touchSlopScale * mTouchSlop); 1418 boolean xMoved = xDiff > touchSlop; 1419 1420 if (xMoved) { 1421 // Scroll if the user moved far enough along the X axis 1422 mTouchState = TOUCH_STATE_SCROLLING; 1423 mTotalMotionX += Math.abs(mLastMotionX - x); 1424 mLastMotionX = x; 1425 mLastMotionXRemainder = 0; 1426 onScrollInteractionBegin(); 1427 pageBeginMoving(); 1428 } 1429 } 1430 1431 protected void cancelCurrentPageLongPress() { 1432 // Try canceling the long press. It could also have been scheduled 1433 // by a distant descendant, so use the mAllowLongPress flag to block 1434 // everything 1435 final View currentPage = getPageAt(mCurrentPage); 1436 if (currentPage != null) { 1437 currentPage.cancelLongPress(); 1438 } 1439 } 1440 1441 protected float getScrollProgress(int screenCenter, View v, int page) { 1442 final int halfScreenSize = getViewportWidth() / 2; 1443 1444 int delta = screenCenter - (getScrollForPage(page) + halfScreenSize); 1445 int count = getChildCount(); 1446 1447 final int totalDistance; 1448 1449 int adjacentPage = page + 1; 1450 if ((delta < 0 && !mIsRtl) || (delta > 0 && mIsRtl)) { 1451 adjacentPage = page - 1; 1452 } 1453 1454 if (adjacentPage < 0 || adjacentPage > count - 1) { 1455 totalDistance = v.getMeasuredWidth() + mPageSpacing; 1456 } else { 1457 totalDistance = Math.abs(getScrollForPage(adjacentPage) - getScrollForPage(page)); 1458 } 1459 1460 float scrollProgress = delta / (totalDistance * 1.0f); 1461 scrollProgress = Math.min(scrollProgress, MAX_SCROLL_PROGRESS); 1462 scrollProgress = Math.max(scrollProgress, - MAX_SCROLL_PROGRESS); 1463 return scrollProgress; 1464 } 1465 1466 public int getScrollForPage(int index) { 1467 if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) { 1468 return 0; 1469 } else { 1470 return mPageScrolls[index]; 1471 } 1472 } 1473 1474 // While layout transitions are occurring, a child's position may stray from its baseline 1475 // position. This method returns the magnitude of this stray at any given time. 1476 public int getLayoutTransitionOffsetForPage(int index) { 1477 if (mPageScrolls == null || index >= mPageScrolls.length || index < 0) { 1478 return 0; 1479 } else { 1480 View child = getChildAt(index); 1481 1482 int scrollOffset = 0; 1483 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1484 if (!lp.isFullScreenPage) { 1485 scrollOffset = mIsRtl ? getPaddingRight() : getPaddingLeft(); 1486 } 1487 1488 int baselineX = mPageScrolls[index] + scrollOffset + getViewportOffsetX(); 1489 return (int) (child.getX() - baselineX); 1490 } 1491 } 1492 1493 protected void dampedOverScroll(float amount) { 1494 int screenSize = getViewportWidth(); 1495 float f = (amount / screenSize); 1496 if (f < 0) { 1497 mEdgeGlowLeft.onPull(-f); 1498 } else if (f > 0) { 1499 mEdgeGlowRight.onPull(f); 1500 } else { 1501 return; 1502 } 1503 invalidate(); 1504 } 1505 1506 protected void overScroll(float amount) { 1507 dampedOverScroll(amount); 1508 } 1509 1510 public void enableFreeScroll() { 1511 setEnableFreeScroll(true); 1512 } 1513 1514 public void disableFreeScroll() { 1515 setEnableFreeScroll(false); 1516 } 1517 1518 void updateFreescrollBounds() { 1519 getFreeScrollPageRange(mTempVisiblePagesRange); 1520 if (mIsRtl) { 1521 mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[1]); 1522 mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[0]); 1523 } else { 1524 mFreeScrollMinScrollX = getScrollForPage(mTempVisiblePagesRange[0]); 1525 mFreeScrollMaxScrollX = getScrollForPage(mTempVisiblePagesRange[1]); 1526 } 1527 } 1528 1529 private void setEnableFreeScroll(boolean freeScroll) { 1530 mFreeScroll = freeScroll; 1531 1532 if (mFreeScroll) { 1533 updateFreescrollBounds(); 1534 getFreeScrollPageRange(mTempVisiblePagesRange); 1535 if (getCurrentPage() < mTempVisiblePagesRange[0]) { 1536 setCurrentPage(mTempVisiblePagesRange[0]); 1537 } else if (getCurrentPage() > mTempVisiblePagesRange[1]) { 1538 setCurrentPage(mTempVisiblePagesRange[1]); 1539 } 1540 } 1541 1542 setEnableOverscroll(!freeScroll); 1543 } 1544 1545 protected void setEnableOverscroll(boolean enable) { 1546 mAllowOverScroll = enable; 1547 } 1548 1549 private int getNearestHoverOverPageIndex() { 1550 if (mDragView != null) { 1551 int dragX = (int) (mDragView.getLeft() + (mDragView.getMeasuredWidth() / 2) 1552 + mDragView.getTranslationX()); 1553 getFreeScrollPageRange(mTempVisiblePagesRange); 1554 int minDistance = Integer.MAX_VALUE; 1555 int minIndex = indexOfChild(mDragView); 1556 for (int i = mTempVisiblePagesRange[0]; i <= mTempVisiblePagesRange[1]; i++) { 1557 View page = getPageAt(i); 1558 int pageX = (int) (page.getLeft() + page.getMeasuredWidth() / 2); 1559 int d = Math.abs(dragX - pageX); 1560 if (d < minDistance) { 1561 minIndex = i; 1562 minDistance = d; 1563 } 1564 } 1565 return minIndex; 1566 } 1567 return -1; 1568 } 1569 1570 @Override 1571 public boolean onTouchEvent(MotionEvent ev) { 1572 super.onTouchEvent(ev); 1573 1574 // Skip touch handling if there are no pages to swipe 1575 if (getChildCount() <= 0) return super.onTouchEvent(ev); 1576 1577 acquireVelocityTrackerAndAddMovement(ev); 1578 1579 final int action = ev.getAction(); 1580 1581 switch (action & MotionEvent.ACTION_MASK) { 1582 case MotionEvent.ACTION_DOWN: 1583 /* 1584 * If being flinged and user touches, stop the fling. isFinished 1585 * will be false if being flinged. 1586 */ 1587 if (!mScroller.isFinished()) { 1588 abortScrollerAnimation(false); 1589 } 1590 1591 // Remember where the motion event started 1592 mDownMotionX = mLastMotionX = ev.getX(); 1593 mDownMotionY = mLastMotionY = ev.getY(); 1594 mDownScrollX = getScrollX(); 1595 float[] p = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); 1596 mParentDownMotionX = p[0]; 1597 mParentDownMotionY = p[1]; 1598 mLastMotionXRemainder = 0; 1599 mTotalMotionX = 0; 1600 mActivePointerId = ev.getPointerId(0); 1601 1602 if (mTouchState == TOUCH_STATE_SCROLLING) { 1603 onScrollInteractionBegin(); 1604 pageBeginMoving(); 1605 } 1606 break; 1607 1608 case MotionEvent.ACTION_MOVE: 1609 if (mTouchState == TOUCH_STATE_SCROLLING) { 1610 // Scroll to follow the motion event 1611 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 1612 1613 if (pointerIndex == -1) return true; 1614 1615 final float x = ev.getX(pointerIndex); 1616 final float deltaX = mLastMotionX + mLastMotionXRemainder - x; 1617 1618 mTotalMotionX += Math.abs(deltaX); 1619 1620 // Only scroll and update mLastMotionX if we have moved some discrete amount. We 1621 // keep the remainder because we are actually testing if we've moved from the last 1622 // scrolled position (which is discrete). 1623 if (Math.abs(deltaX) >= 1.0f) { 1624 scrollBy((int) deltaX, 0); 1625 mLastMotionX = x; 1626 mLastMotionXRemainder = deltaX - (int) deltaX; 1627 } else { 1628 awakenScrollBars(); 1629 } 1630 } else if (mTouchState == TOUCH_STATE_REORDERING) { 1631 // Update the last motion position 1632 mLastMotionX = ev.getX(); 1633 mLastMotionY = ev.getY(); 1634 1635 // Update the parent down so that our zoom animations take this new movement into 1636 // account 1637 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); 1638 mParentDownMotionX = pt[0]; 1639 mParentDownMotionY = pt[1]; 1640 updateDragViewTranslationDuringDrag(); 1641 1642 // Find the closest page to the touch point 1643 final int dragViewIndex = indexOfChild(mDragView); 1644 1645 if (DEBUG) Log.d(TAG, "mLastMotionX: " + mLastMotionX); 1646 if (DEBUG) Log.d(TAG, "mLastMotionY: " + mLastMotionY); 1647 if (DEBUG) Log.d(TAG, "mParentDownMotionX: " + mParentDownMotionX); 1648 if (DEBUG) Log.d(TAG, "mParentDownMotionY: " + mParentDownMotionY); 1649 1650 final int pageUnderPointIndex = getNearestHoverOverPageIndex(); 1651 if (pageUnderPointIndex > -1 && pageUnderPointIndex != indexOfChild(mDragView)) { 1652 mTempVisiblePagesRange[0] = 0; 1653 mTempVisiblePagesRange[1] = getPageCount() - 1; 1654 getFreeScrollPageRange(mTempVisiblePagesRange); 1655 if (mTempVisiblePagesRange[0] <= pageUnderPointIndex && 1656 pageUnderPointIndex <= mTempVisiblePagesRange[1] && 1657 pageUnderPointIndex != mSidePageHoverIndex && mScroller.isFinished()) { 1658 mSidePageHoverIndex = pageUnderPointIndex; 1659 mSidePageHoverRunnable = new Runnable() { 1660 @Override 1661 public void run() { 1662 // Setup the scroll to the correct page before we swap the views 1663 snapToPage(pageUnderPointIndex); 1664 1665 // For each of the pages between the paged view and the drag view, 1666 // animate them from the previous position to the new position in 1667 // the layout (as a result of the drag view moving in the layout) 1668 int shiftDelta = (dragViewIndex < pageUnderPointIndex) ? -1 : 1; 1669 int lowerIndex = (dragViewIndex < pageUnderPointIndex) ? 1670 dragViewIndex + 1 : pageUnderPointIndex; 1671 int upperIndex = (dragViewIndex > pageUnderPointIndex) ? 1672 dragViewIndex - 1 : pageUnderPointIndex; 1673 for (int i = lowerIndex; i <= upperIndex; ++i) { 1674 View v = getChildAt(i); 1675 // dragViewIndex < pageUnderPointIndex, so after we remove the 1676 // drag view all subsequent views to pageUnderPointIndex will 1677 // shift down. 1678 int oldX = getViewportOffsetX() + getChildOffset(i); 1679 int newX = getViewportOffsetX() + getChildOffset(i + shiftDelta); 1680 1681 // Animate the view translation from its old position to its new 1682 // position 1683 ObjectAnimator anim = (ObjectAnimator) v.getTag(); 1684 if (anim != null) { 1685 anim.cancel(); 1686 } 1687 1688 v.setTranslationX(oldX - newX); 1689 anim = LauncherAnimUtils.ofFloat(v, View.TRANSLATION_X, 0); 1690 anim.setDuration(REORDERING_REORDER_REPOSITION_DURATION); 1691 anim.start(); 1692 v.setTag(anim); 1693 } 1694 1695 removeView(mDragView); 1696 addView(mDragView, pageUnderPointIndex); 1697 mSidePageHoverIndex = -1; 1698 if (mPageIndicator != null) { 1699 mPageIndicator.setActiveMarker(getNextPage()); 1700 } 1701 } 1702 }; 1703 postDelayed(mSidePageHoverRunnable, REORDERING_SIDE_PAGE_HOVER_TIMEOUT); 1704 } 1705 } else { 1706 removeCallbacks(mSidePageHoverRunnable); 1707 mSidePageHoverIndex = -1; 1708 } 1709 } else { 1710 determineScrollingStart(ev); 1711 } 1712 break; 1713 1714 case MotionEvent.ACTION_UP: 1715 if (mTouchState == TOUCH_STATE_SCROLLING) { 1716 final int activePointerId = mActivePointerId; 1717 final int pointerIndex = ev.findPointerIndex(activePointerId); 1718 final float x = ev.getX(pointerIndex); 1719 final VelocityTracker velocityTracker = mVelocityTracker; 1720 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1721 int velocityX = (int) velocityTracker.getXVelocity(activePointerId); 1722 final int deltaX = (int) (x - mDownMotionX); 1723 final int pageWidth = getPageAt(mCurrentPage).getMeasuredWidth(); 1724 boolean isSignificantMove = Math.abs(deltaX) > pageWidth * 1725 SIGNIFICANT_MOVE_THRESHOLD; 1726 1727 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); 1728 1729 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && 1730 Math.abs(velocityX) > mFlingThresholdVelocity; 1731 1732 if (!mFreeScroll) { 1733 // In the case that the page is moved far to one direction and then is flung 1734 // in the opposite direction, we use a threshold to determine whether we should 1735 // just return to the starting page, or if we should skip one further. 1736 boolean returnToOriginalPage = false; 1737 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && 1738 Math.signum(velocityX) != Math.signum(deltaX) && isFling) { 1739 returnToOriginalPage = true; 1740 } 1741 1742 int finalPage; 1743 // We give flings precedence over large moves, which is why we short-circuit our 1744 // test for a large move if a fling has been registered. That is, a large 1745 // move to the left and fling to the right will register as a fling to the right. 1746 boolean isDeltaXLeft = mIsRtl ? deltaX > 0 : deltaX < 0; 1747 boolean isVelocityXLeft = mIsRtl ? velocityX > 0 : velocityX < 0; 1748 if (((isSignificantMove && !isDeltaXLeft && !isFling) || 1749 (isFling && !isVelocityXLeft)) && mCurrentPage > 0) { 1750 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; 1751 snapToPageWithVelocity(finalPage, velocityX); 1752 } else if (((isSignificantMove && isDeltaXLeft && !isFling) || 1753 (isFling && isVelocityXLeft)) && 1754 mCurrentPage < getChildCount() - 1) { 1755 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; 1756 snapToPageWithVelocity(finalPage, velocityX); 1757 } else { 1758 snapToDestination(); 1759 } 1760 } else { 1761 if (!mScroller.isFinished()) { 1762 abortScrollerAnimation(true); 1763 } 1764 1765 float scaleX = getScaleX(); 1766 int vX = (int) (-velocityX * scaleX); 1767 int initialScrollX = (int) (getScrollX() * scaleX); 1768 1769 mScroller.setInterpolator(mDefaultInterpolator); 1770 mScroller.fling(initialScrollX, 1771 getScrollY(), vX, 0, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0); 1772 invalidate(); 1773 } 1774 onScrollInteractionEnd(); 1775 } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { 1776 // at this point we have not moved beyond the touch slop 1777 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 1778 // we can just page 1779 int nextPage = Math.max(0, mCurrentPage - 1); 1780 if (nextPage != mCurrentPage) { 1781 snapToPage(nextPage); 1782 } else { 1783 snapToDestination(); 1784 } 1785 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) { 1786 // at this point we have not moved beyond the touch slop 1787 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 1788 // we can just page 1789 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); 1790 if (nextPage != mCurrentPage) { 1791 snapToPage(nextPage); 1792 } else { 1793 snapToDestination(); 1794 } 1795 } else if (mTouchState == TOUCH_STATE_REORDERING) { 1796 // Update the last motion position 1797 mLastMotionX = ev.getX(); 1798 mLastMotionY = ev.getY(); 1799 1800 // Update the parent down so that our zoom animations take this new movement into 1801 // account 1802 float[] pt = mapPointFromViewToParent(this, mLastMotionX, mLastMotionY); 1803 mParentDownMotionX = pt[0]; 1804 mParentDownMotionY = pt[1]; 1805 updateDragViewTranslationDuringDrag(); 1806 } else { 1807 if (!mCancelTap) { 1808 onUnhandledTap(ev); 1809 } 1810 } 1811 1812 // Remove the callback to wait for the side page hover timeout 1813 removeCallbacks(mSidePageHoverRunnable); 1814 // End any intermediate reordering states 1815 resetTouchState(); 1816 break; 1817 1818 case MotionEvent.ACTION_CANCEL: 1819 if (mTouchState == TOUCH_STATE_SCROLLING) { 1820 snapToDestination(); 1821 } 1822 resetTouchState(); 1823 break; 1824 1825 case MotionEvent.ACTION_POINTER_UP: 1826 onSecondaryPointerUp(ev); 1827 releaseVelocityTracker(); 1828 break; 1829 } 1830 1831 return true; 1832 } 1833 1834 private void resetTouchState() { 1835 releaseVelocityTracker(); 1836 endReordering(); 1837 mCancelTap = false; 1838 mTouchState = TOUCH_STATE_REST; 1839 mActivePointerId = INVALID_POINTER; 1840 mEdgeGlowLeft.onRelease(); 1841 mEdgeGlowRight.onRelease(); 1842 } 1843 1844 /** 1845 * Triggered by scrolling via touch 1846 */ 1847 protected void onScrollInteractionBegin() { 1848 } 1849 1850 protected void onScrollInteractionEnd() { 1851 } 1852 1853 protected void onUnhandledTap(MotionEvent ev) { 1854 ((Launcher) getContext()).onClick(this); 1855 } 1856 1857 @Override 1858 public boolean onGenericMotionEvent(MotionEvent event) { 1859 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 1860 switch (event.getAction()) { 1861 case MotionEvent.ACTION_SCROLL: { 1862 // Handle mouse (or ext. device) by shifting the page depending on the scroll 1863 final float vscroll; 1864 final float hscroll; 1865 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { 1866 vscroll = 0; 1867 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 1868 } else { 1869 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); 1870 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 1871 } 1872 if (hscroll != 0 || vscroll != 0) { 1873 boolean isForwardScroll = mIsRtl ? (hscroll < 0 || vscroll < 0) 1874 : (hscroll > 0 || vscroll > 0); 1875 if (isForwardScroll) { 1876 scrollRight(); 1877 } else { 1878 scrollLeft(); 1879 } 1880 return true; 1881 } 1882 } 1883 } 1884 } 1885 return super.onGenericMotionEvent(event); 1886 } 1887 1888 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { 1889 if (mVelocityTracker == null) { 1890 mVelocityTracker = VelocityTracker.obtain(); 1891 } 1892 mVelocityTracker.addMovement(ev); 1893 } 1894 1895 private void releaseVelocityTracker() { 1896 if (mVelocityTracker != null) { 1897 mVelocityTracker.clear(); 1898 mVelocityTracker.recycle(); 1899 mVelocityTracker = null; 1900 } 1901 } 1902 1903 private void onSecondaryPointerUp(MotionEvent ev) { 1904 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 1905 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 1906 final int pointerId = ev.getPointerId(pointerIndex); 1907 if (pointerId == mActivePointerId) { 1908 // This was our active pointer going up. Choose a new 1909 // active pointer and adjust accordingly. 1910 // TODO: Make this decision more intelligent. 1911 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1912 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); 1913 mLastMotionY = ev.getY(newPointerIndex); 1914 mLastMotionXRemainder = 0; 1915 mActivePointerId = ev.getPointerId(newPointerIndex); 1916 if (mVelocityTracker != null) { 1917 mVelocityTracker.clear(); 1918 } 1919 } 1920 } 1921 1922 @Override 1923 public void requestChildFocus(View child, View focused) { 1924 super.requestChildFocus(child, focused); 1925 int page = indexToPage(indexOfChild(child)); 1926 if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) { 1927 snapToPage(page); 1928 } 1929 } 1930 1931 int getPageNearestToCenterOfScreen() { 1932 int minDistanceFromScreenCenter = Integer.MAX_VALUE; 1933 int minDistanceFromScreenCenterIndex = -1; 1934 int screenCenter = getViewportOffsetX() + getScrollX() + (getViewportWidth() / 2); 1935 final int childCount = getChildCount(); 1936 for (int i = 0; i < childCount; ++i) { 1937 View layout = (View) getPageAt(i); 1938 int childWidth = layout.getMeasuredWidth(); 1939 int halfChildWidth = (childWidth / 2); 1940 int childCenter = getViewportOffsetX() + getChildOffset(i) + halfChildWidth; 1941 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); 1942 if (distanceFromScreenCenter < minDistanceFromScreenCenter) { 1943 minDistanceFromScreenCenter = distanceFromScreenCenter; 1944 minDistanceFromScreenCenterIndex = i; 1945 } 1946 } 1947 return minDistanceFromScreenCenterIndex; 1948 } 1949 1950 protected void snapToDestination() { 1951 snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); 1952 } 1953 1954 private static class ScrollInterpolator implements Interpolator { 1955 public ScrollInterpolator() { 1956 } 1957 1958 public float getInterpolation(float t) { 1959 t -= 1.0f; 1960 return t*t*t*t*t + 1; 1961 } 1962 } 1963 1964 // We want the duration of the page snap animation to be influenced by the distance that 1965 // the screen has to travel, however, we don't want this duration to be effected in a 1966 // purely linear fashion. Instead, we use this method to moderate the effect that the distance 1967 // of travel has on the overall snap duration. 1968 private float distanceInfluenceForSnapDuration(float f) { 1969 f -= 0.5f; // center the values about 0. 1970 f *= 0.3f * Math.PI / 2.0f; 1971 return (float) Math.sin(f); 1972 } 1973 1974 protected void snapToPageWithVelocity(int whichPage, int velocity) { 1975 whichPage = validateNewPage(whichPage); 1976 int halfScreenSize = getViewportWidth() / 2; 1977 1978 final int newX = getScrollForPage(whichPage); 1979 int delta = newX - getScrollX(); 1980 int duration = 0; 1981 1982 if (Math.abs(velocity) < mMinFlingVelocity) { 1983 // If the velocity is low enough, then treat this more as an automatic page advance 1984 // as opposed to an apparent physical response to flinging 1985 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); 1986 return; 1987 } 1988 1989 // Here we compute a "distance" that will be used in the computation of the overall 1990 // snap duration. This is a function of the actual distance that needs to be traveled; 1991 // we keep this value close to half screen size in order to reduce the variance in snap 1992 // duration as a function of the distance the page needs to travel. 1993 float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize)); 1994 float distance = halfScreenSize + halfScreenSize * 1995 distanceInfluenceForSnapDuration(distanceRatio); 1996 1997 velocity = Math.abs(velocity); 1998 velocity = Math.max(mMinSnapVelocity, velocity); 1999 2000 // we want the page's snap velocity to approximately match the velocity at which the 2001 // user flings, so we scale the duration by a value near to the derivative of the scroll 2002 // interpolator at zero, ie. 5. We use 4 to make it a little slower. 2003 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 2004 2005 snapToPage(whichPage, delta, duration); 2006 } 2007 2008 public void snapToPage(int whichPage) { 2009 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); 2010 } 2011 2012 protected void snapToPageImmediately(int whichPage) { 2013 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION, true, null); 2014 } 2015 2016 protected void snapToPage(int whichPage, int duration) { 2017 snapToPage(whichPage, duration, false, null); 2018 } 2019 2020 protected void snapToPage(int whichPage, int duration, TimeInterpolator interpolator) { 2021 snapToPage(whichPage, duration, false, interpolator); 2022 } 2023 2024 protected void snapToPage(int whichPage, int duration, boolean immediate, 2025 TimeInterpolator interpolator) { 2026 whichPage = validateNewPage(whichPage); 2027 2028 int newX = getScrollForPage(whichPage); 2029 final int delta = newX - getScrollX(); 2030 snapToPage(whichPage, delta, duration, immediate, interpolator); 2031 } 2032 2033 protected void snapToPage(int whichPage, int delta, int duration) { 2034 snapToPage(whichPage, delta, duration, false, null); 2035 } 2036 2037 protected void snapToPage(int whichPage, int delta, int duration, boolean immediate, 2038 TimeInterpolator interpolator) { 2039 whichPage = validateNewPage(whichPage); 2040 2041 mNextPage = whichPage; 2042 View focusedChild = getFocusedChild(); 2043 if (focusedChild != null && whichPage != mCurrentPage && 2044 focusedChild == getPageAt(mCurrentPage)) { 2045 focusedChild.clearFocus(); 2046 } 2047 2048 pageBeginMoving(); 2049 awakenScrollBars(duration); 2050 if (immediate) { 2051 duration = 0; 2052 } else if (duration == 0) { 2053 duration = Math.abs(delta); 2054 } 2055 2056 if (!mScroller.isFinished()) { 2057 abortScrollerAnimation(false); 2058 } 2059 2060 if (interpolator != null) { 2061 mScroller.setInterpolator(interpolator); 2062 } else { 2063 mScroller.setInterpolator(mDefaultInterpolator); 2064 } 2065 2066 mScroller.startScroll(getScrollX(), 0, delta, 0, duration); 2067 2068 updatePageIndicator(); 2069 2070 // Trigger a compute() to finish switching pages if necessary 2071 if (immediate) { 2072 computeScroll(); 2073 } 2074 2075 mForceScreenScrolled = true; 2076 invalidate(); 2077 } 2078 2079 public void scrollLeft() { 2080 if (getNextPage() > 0) snapToPage(getNextPage() - 1); 2081 } 2082 2083 public void scrollRight() { 2084 if (getNextPage() < getChildCount() -1) snapToPage(getNextPage() + 1); 2085 } 2086 2087 public int getPageForView(View v) { 2088 int result = -1; 2089 if (v != null) { 2090 ViewParent vp = v.getParent(); 2091 int count = getChildCount(); 2092 for (int i = 0; i < count; i++) { 2093 if (vp == getPageAt(i)) { 2094 return i; 2095 } 2096 } 2097 } 2098 return result; 2099 } 2100 2101 @Override 2102 public boolean performLongClick() { 2103 mCancelTap = true; 2104 return super.performLongClick(); 2105 } 2106 2107 public static class SavedState extends BaseSavedState { 2108 int currentPage = -1; 2109 2110 SavedState(Parcelable superState) { 2111 super(superState); 2112 } 2113 2114 @Thunk SavedState(Parcel in) { 2115 super(in); 2116 currentPage = in.readInt(); 2117 } 2118 2119 @Override 2120 public void writeToParcel(Parcel out, int flags) { 2121 super.writeToParcel(out, flags); 2122 out.writeInt(currentPage); 2123 } 2124 2125 public static final Parcelable.Creator<SavedState> CREATOR = 2126 new Parcelable.Creator<SavedState>() { 2127 public SavedState createFromParcel(Parcel in) { 2128 return new SavedState(in); 2129 } 2130 2131 public SavedState[] newArray(int size) { 2132 return new SavedState[size]; 2133 } 2134 }; 2135 } 2136 2137 // Animate the drag view back to the original position 2138 private void animateDragViewToOriginalPosition() { 2139 if (mDragView != null) { 2140 Animator anim = new LauncherViewPropertyAnimator(mDragView) 2141 .translationX(0) 2142 .translationY(0) 2143 .scaleX(1) 2144 .scaleY(1) 2145 .setDuration(REORDERING_DROP_REPOSITION_DURATION); 2146 anim.addListener(new AnimatorListenerAdapter() { 2147 @Override 2148 public void onAnimationEnd(Animator animation) { 2149 onPostReorderingAnimationCompleted(); 2150 } 2151 }); 2152 anim.start(); 2153 } 2154 } 2155 2156 public void onStartReordering() { 2157 // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.) 2158 mTouchState = TOUCH_STATE_REORDERING; 2159 mIsReordering = true; 2160 2161 // We must invalidate to trigger a redraw to update the layers such that the drag view 2162 // is always drawn on top 2163 invalidate(); 2164 } 2165 2166 @Thunk void onPostReorderingAnimationCompleted() { 2167 // Trigger the callback when reordering has settled 2168 --mPostReorderingPreZoomInRemainingAnimationCount; 2169 if (mPostReorderingPreZoomInRunnable != null && 2170 mPostReorderingPreZoomInRemainingAnimationCount == 0) { 2171 mPostReorderingPreZoomInRunnable.run(); 2172 mPostReorderingPreZoomInRunnable = null; 2173 } 2174 } 2175 2176 public void onEndReordering() { 2177 mIsReordering = false; 2178 } 2179 2180 public boolean startReordering(View v) { 2181 int dragViewIndex = indexOfChild(v); 2182 2183 if (mTouchState != TOUCH_STATE_REST || dragViewIndex == -1) return false; 2184 2185 mTempVisiblePagesRange[0] = 0; 2186 mTempVisiblePagesRange[1] = getPageCount() - 1; 2187 getFreeScrollPageRange(mTempVisiblePagesRange); 2188 mReorderingStarted = true; 2189 2190 // Check if we are within the reordering range 2191 if (mTempVisiblePagesRange[0] <= dragViewIndex && 2192 dragViewIndex <= mTempVisiblePagesRange[1]) { 2193 // Find the drag view under the pointer 2194 mDragView = getChildAt(dragViewIndex); 2195 mDragView.animate().scaleX(1.15f).scaleY(1.15f).setDuration(100).start(); 2196 mDragViewBaselineLeft = mDragView.getLeft(); 2197 snapToPage(getPageNearestToCenterOfScreen()); 2198 disableFreeScroll(); 2199 onStartReordering(); 2200 return true; 2201 } 2202 return false; 2203 } 2204 2205 boolean isReordering(boolean testTouchState) { 2206 boolean state = mIsReordering; 2207 if (testTouchState) { 2208 state &= (mTouchState == TOUCH_STATE_REORDERING); 2209 } 2210 return state; 2211 } 2212 void endReordering() { 2213 // For simplicity, we call endReordering sometimes even if reordering was never started. 2214 // In that case, we don't want to do anything. 2215 if (!mReorderingStarted) return; 2216 mReorderingStarted = false; 2217 2218 // If we haven't flung-to-delete the current child, then we just animate the drag view 2219 // back into position 2220 final Runnable onCompleteRunnable = new Runnable() { 2221 @Override 2222 public void run() { 2223 onEndReordering(); 2224 } 2225 }; 2226 2227 mPostReorderingPreZoomInRunnable = new Runnable() { 2228 public void run() { 2229 onCompleteRunnable.run(); 2230 enableFreeScroll(); 2231 }; 2232 }; 2233 2234 mPostReorderingPreZoomInRemainingAnimationCount = 2235 NUM_ANIMATIONS_RUNNING_BEFORE_ZOOM_OUT; 2236 // Snap to the current page 2237 snapToPage(indexOfChild(mDragView), 0); 2238 // Animate the drag view back to the front position 2239 animateDragViewToOriginalPosition(); 2240 } 2241 2242 /* Accessibility */ 2243 @SuppressWarnings("deprecation") 2244 @TargetApi(Build.VERSION_CODES.LOLLIPOP) 2245 @Override 2246 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 2247 super.onInitializeAccessibilityNodeInfo(info); 2248 info.setScrollable(getPageCount() > 1); 2249 if (getCurrentPage() < getPageCount() - 1) { 2250 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 2251 } 2252 if (getCurrentPage() > 0) { 2253 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 2254 } 2255 info.setClassName(getClass().getName()); 2256 2257 // Accessibility-wise, PagedView doesn't support long click, so disabling it. 2258 // Besides disabling the accessibility long-click, this also prevents this view from getting 2259 // accessibility focus. 2260 info.setLongClickable(false); 2261 if (Utilities.ATLEAST_LOLLIPOP) { 2262 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK); 2263 } 2264 } 2265 2266 @Override 2267 public void sendAccessibilityEvent(int eventType) { 2268 // Don't let the view send real scroll events. 2269 if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED) { 2270 super.sendAccessibilityEvent(eventType); 2271 } 2272 } 2273 2274 @Override 2275 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 2276 super.onInitializeAccessibilityEvent(event); 2277 event.setScrollable(getPageCount() > 1); 2278 } 2279 2280 @Override 2281 public boolean performAccessibilityAction(int action, Bundle arguments) { 2282 if (super.performAccessibilityAction(action, arguments)) { 2283 return true; 2284 } 2285 switch (action) { 2286 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 2287 if (getCurrentPage() < getPageCount() - 1) { 2288 scrollRight(); 2289 return true; 2290 } 2291 } break; 2292 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 2293 if (getCurrentPage() > 0) { 2294 scrollLeft(); 2295 return true; 2296 } 2297 } break; 2298 } 2299 return false; 2300 } 2301 2302 protected String getCurrentPageDescription() { 2303 return String.format(getContext().getString(R.string.default_scroll_format), 2304 getNextPage() + 1, getChildCount()); 2305 } 2306 2307 @Override 2308 public boolean onHoverEvent(android.view.MotionEvent event) { 2309 return true; 2310 } 2311} 2312