1 /* 2 * Copyright (C) 2010 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 17 package com.android.launcher2; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorInflater; 21 import android.animation.AnimatorListenerAdapter; 22 import android.animation.ObjectAnimator; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.content.res.TypedArray; 26 import android.graphics.Canvas; 27 import android.graphics.Rect; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 import android.view.ActionMode; 33 import android.view.InputDevice; 34 import android.view.KeyEvent; 35 import android.view.MotionEvent; 36 import android.view.VelocityTracker; 37 import android.view.View; 38 import android.view.ViewConfiguration; 39 import android.view.ViewGroup; 40 import android.view.ViewParent; 41 import android.view.accessibility.AccessibilityEvent; 42 import android.view.accessibility.AccessibilityManager; 43 import android.view.accessibility.AccessibilityNodeInfo; 44 import android.view.animation.Interpolator; 45 import android.widget.Checkable; 46 import android.widget.ImageView; 47 import android.widget.Scroller; 48 49 import com.android.launcher.R; 50 51 import java.util.ArrayList; 52 53 /** 54 * An abstraction of the original Workspace which supports browsing through a 55 * sequential list of "pages" 56 */ 57 public abstract class PagedView extends ViewGroup { 58 private static final String TAG = "PagedView"; 59 private static final boolean DEBUG = false; 60 protected static final int INVALID_PAGE = -1; 61 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; 64 65 private static final int PAGE_SNAP_ANIMATION_DURATION = 550; 66 protected static final float NANOTIME_DIV = 1000000000.0f; 67 68 private static final float OVERSCROLL_ACCELERATE_FACTOR = 2; 69 private static final float OVERSCROLL_DAMP_FACTOR = 0.14f; 70 private static final int MINIMUM_SNAP_VELOCITY = 2200; 71 private static final int MIN_FLING_VELOCITY = 250; 72 private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; 73 // The page is moved more than halfway, automatically move to the next page on touch up. 74 private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; 75 76 // the velocity at which a fling gesture will cause us to snap to the next page 77 protected int mSnapVelocity = 500; 78 79 protected float mDensity; 80 protected float mSmoothingTime; 81 protected float mTouchX; 82 83 protected boolean mFirstLayout = true; 84 85 protected int mCurrentPage; 86 protected int mNextPage = INVALID_PAGE; 87 protected int mMaxScrollX; 88 protected Scroller mScroller; 89 private VelocityTracker mVelocityTracker; 90 91 private float mDownMotionX; 92 protected float mLastMotionX; 93 protected float mLastMotionXRemainder; 94 protected float mLastMotionY; 95 protected float mTotalMotionX; 96 private int mLastScreenCenter = -1; 97 private int[] mChildOffsets; 98 private int[] mChildRelativeOffsets; 99 private int[] mChildOffsetsWithLayoutScale; 100 101 protected final static int TOUCH_STATE_REST = 0; 102 protected final static int TOUCH_STATE_SCROLLING = 1; 103 protected final static int TOUCH_STATE_PREV_PAGE = 2; 104 protected final static int TOUCH_STATE_NEXT_PAGE = 3; 105 protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f; 106 107 protected int mTouchState = TOUCH_STATE_REST; 108 protected boolean mForceScreenScrolled = false; 109 110 protected OnLongClickListener mLongClickListener; 111 112 protected boolean mAllowLongPress = true; 113 114 protected int mTouchSlop; 115 private int mPagingTouchSlop; 116 private int mMaximumVelocity; 117 private int mMinimumWidth; 118 protected int mPageSpacing; 119 protected int mPageLayoutPaddingTop; 120 protected int mPageLayoutPaddingBottom; 121 protected int mPageLayoutPaddingLeft; 122 protected int mPageLayoutPaddingRight; 123 protected int mPageLayoutWidthGap; 124 protected int mPageLayoutHeightGap; 125 protected int mCellCountX = 0; 126 protected int mCellCountY = 0; 127 protected boolean mCenterPagesVertically; 128 protected boolean mAllowOverScroll = true; 129 protected int mUnboundedScrollX; 130 protected int[] mTempVisiblePagesRange = new int[2]; 131 132 // mOverScrollX is equal to mScrollX when we're within the normal scroll range. Otherwise 133 // it is equal to the scaled overscroll position. We use a separate value so as to prevent 134 // the screens from continuing to translate beyond the normal bounds. 135 protected int mOverScrollX; 136 137 // parameter that adjusts the layout to be optimized for pages with that scale factor 138 protected float mLayoutScale = 1.0f; 139 140 protected static final int INVALID_POINTER = -1; 141 142 protected int mActivePointerId = INVALID_POINTER; 143 144 private PageSwitchListener mPageSwitchListener; 145 146 protected ArrayList<Boolean> mDirtyPageContent; 147 148 // choice modes 149 protected static final int CHOICE_MODE_NONE = 0; 150 protected static final int CHOICE_MODE_SINGLE = 1; 151 // Multiple selection mode is not supported by all Launcher actions atm 152 protected static final int CHOICE_MODE_MULTIPLE = 2; 153 154 protected int mChoiceMode; 155 private ActionMode mActionMode; 156 157 // If true, syncPages and syncPageItems will be called to refresh pages 158 protected boolean mContentIsRefreshable = true; 159 160 // If true, modify alpha of neighboring pages as user scrolls left/right 161 protected boolean mFadeInAdjacentScreens = true; 162 163 // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding 164 // to switch to a new page 165 protected boolean mUsePagingTouchSlop = true; 166 167 // If true, the subclass should directly update mScrollX itself in its computeScroll method 168 // (SmoothPagedView does this) 169 protected boolean mDeferScrollUpdate = false; 170 171 protected boolean mIsPageMoving = false; 172 173 // All syncs and layout passes are deferred until data is ready. 174 protected boolean mIsDataReady = false; 175 176 // Scrolling indicator 177 private ValueAnimator mScrollIndicatorAnimator; 178 private ImageView mScrollIndicator; 179 private int mScrollIndicatorPaddingLeft; 180 private int mScrollIndicatorPaddingRight; 181 private boolean mHasScrollIndicator = true; 182 protected static final int sScrollIndicatorFadeInDuration = 150; 183 protected static final int sScrollIndicatorFadeOutDuration = 650; 184 protected static final int sScrollIndicatorFlashDuration = 650; 185 186 // If set, will defer loading associated pages until the scrolling settles 187 private boolean mDeferLoadAssociatedPagesUntilScrollCompletes; 188 189 public interface PageSwitchListener { 190 void onPageSwitch(View newPage, int newPageIndex); 191 } 192 193 public PagedView(Context context) { 194 this(context, null); 195 } 196 197 public PagedView(Context context, AttributeSet attrs) { 198 this(context, attrs, 0); 199 } 200 201 public PagedView(Context context, AttributeSet attrs, int defStyle) { 202 super(context, attrs, defStyle); 203 mChoiceMode = CHOICE_MODE_NONE; 204 205 TypedArray a = context.obtainStyledAttributes(attrs, 206 R.styleable.PagedView, defStyle, 0); 207 setPageSpacing(a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0)); 208 mPageLayoutPaddingTop = a.getDimensionPixelSize( 209 R.styleable.PagedView_pageLayoutPaddingTop, 0); 210 mPageLayoutPaddingBottom = a.getDimensionPixelSize( 211 R.styleable.PagedView_pageLayoutPaddingBottom, 0); 212 mPageLayoutPaddingLeft = a.getDimensionPixelSize( 213 R.styleable.PagedView_pageLayoutPaddingLeft, 0); 214 mPageLayoutPaddingRight = a.getDimensionPixelSize( 215 R.styleable.PagedView_pageLayoutPaddingRight, 0); 216 mPageLayoutWidthGap = a.getDimensionPixelSize( 217 R.styleable.PagedView_pageLayoutWidthGap, 0); 218 mPageLayoutHeightGap = a.getDimensionPixelSize( 219 R.styleable.PagedView_pageLayoutHeightGap, 0); 220 mScrollIndicatorPaddingLeft = 221 a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingLeft, 0); 222 mScrollIndicatorPaddingRight = 223 a.getDimensionPixelSize(R.styleable.PagedView_scrollIndicatorPaddingRight, 0); 224 a.recycle(); 225 226 setHapticFeedbackEnabled(false); 227 init(); 228 } 229 230 /** 231 * Initializes various states for this workspace. 232 */ 233 protected void init() { 234 mDirtyPageContent = new ArrayList<Boolean>(); 235 mDirtyPageContent.ensureCapacity(32); 236 mScroller = new Scroller(getContext(), new ScrollInterpolator()); 237 mCurrentPage = 0; 238 mCenterPagesVertically = true; 239 240 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 241 mTouchSlop = configuration.getScaledTouchSlop(); 242 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 243 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 244 mDensity = getResources().getDisplayMetrics().density; 245 } 246 247 public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { 248 mPageSwitchListener = pageSwitchListener; 249 if (mPageSwitchListener != null) { 250 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); 251 } 252 } 253 254 /** 255 * Called by subclasses to mark that data is ready, and that we can begin loading and laying 256 * out pages. 257 */ 258 protected void setDataIsReady() { 259 mIsDataReady = true; 260 } 261 protected boolean isDataReady() { 262 return mIsDataReady; 263 } 264 265 /** 266 * Returns the index of the currently displayed page. 267 * 268 * @return The index of the currently displayed page. 269 */ 270 int getCurrentPage() { 271 return mCurrentPage; 272 } 273 274 int getPageCount() { 275 return getChildCount(); 276 } 277 278 View getPageAt(int index) { 279 return getChildAt(index); 280 } 281 282 protected int indexToPage(int index) { 283 return index; 284 } 285 286 /** 287 * Updates the scroll of the current page immediately to its final scroll position. We use this 288 * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of 289 * the previous tab page. 290 */ 291 protected void updateCurrentPageScroll() { 292 int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage); 293 scrollTo(newX, 0); 294 mScroller.setFinalX(newX); 295 } 296 297 /** 298 * Sets the current page. 299 */ 300 void setCurrentPage(int currentPage) { 301 if (!mScroller.isFinished()) { 302 mScroller.abortAnimation(); 303 } 304 // don't introduce any checks like mCurrentPage == currentPage here-- if we change the 305 // the default 306 if (getChildCount() == 0) { 307 return; 308 } 309 310 mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1)); 311 updateCurrentPageScroll(); 312 updateScrollingIndicator(); 313 notifyPageSwitchListener(); 314 invalidate(); 315 } 316 317 protected void notifyPageSwitchListener() { 318 if (mPageSwitchListener != null) { 319 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); 320 } 321 } 322 323 protected void pageBeginMoving() { 324 if (!mIsPageMoving) { 325 mIsPageMoving = true; 326 onPageBeginMoving(); 327 } 328 } 329 330 protected void pageEndMoving() { 331 if (mIsPageMoving) { 332 mIsPageMoving = false; 333 onPageEndMoving(); 334 } 335 } 336 337 protected boolean isPageMoving() { 338 return mIsPageMoving; 339 } 340 341 // a method that subclasses can override to add behavior 342 protected void onPageBeginMoving() { 343 showScrollingIndicator(false); 344 } 345 346 // a method that subclasses can override to add behavior 347 protected void onPageEndMoving() { 348 hideScrollingIndicator(false); 349 } 350 351 /** 352 * Registers the specified listener on each page contained in this workspace. 353 * 354 * @param l The listener used to respond to long clicks. 355 */ 356 @Override 357 public void setOnLongClickListener(OnLongClickListener l) { 358 mLongClickListener = l; 359 final int count = getPageCount(); 360 for (int i = 0; i < count; i++) { 361 getPageAt(i).setOnLongClickListener(l); 362 } 363 } 364 365 @Override 366 public void scrollBy(int x, int y) { 367 scrollTo(mUnboundedScrollX + x, mScrollY + y); 368 } 369 370 @Override 371 public void scrollTo(int x, int y) { 372 mUnboundedScrollX = x; 373 374 if (x < 0) { 375 super.scrollTo(0, y); 376 if (mAllowOverScroll) { 377 overScroll(x); 378 } 379 } else if (x > mMaxScrollX) { 380 super.scrollTo(mMaxScrollX, y); 381 if (mAllowOverScroll) { 382 overScroll(x - mMaxScrollX); 383 } 384 } else { 385 mOverScrollX = x; 386 super.scrollTo(x, y); 387 } 388 389 mTouchX = x; 390 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 391 } 392 393 // we moved this functionality to a helper function so SmoothPagedView can reuse it 394 protected boolean computeScrollHelper() { 395 if (mScroller.computeScrollOffset()) { 396 // Don't bother scrolling if the page does not need to be moved 397 if (mScrollX != mScroller.getCurrX() || mScrollY != mScroller.getCurrY()) { 398 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 399 } 400 invalidate(); 401 return true; 402 } else if (mNextPage != INVALID_PAGE) { 403 mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); 404 mNextPage = INVALID_PAGE; 405 notifyPageSwitchListener(); 406 407 // Load the associated pages if necessary 408 if (mDeferLoadAssociatedPagesUntilScrollCompletes) { 409 loadAssociatedPages(mCurrentPage); 410 mDeferLoadAssociatedPagesUntilScrollCompletes = false; 411 } 412 413 // We don't want to trigger a page end moving unless the page has settled 414 // and the user has stopped scrolling 415 if (mTouchState == TOUCH_STATE_REST) { 416 pageEndMoving(); 417 } 418 419 // Notify the user when the page changes 420 if (AccessibilityManager.getInstance(getContext()).isEnabled()) { 421 AccessibilityEvent ev = 422 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED); 423 ev.getText().add(getCurrentPageDescription()); 424 sendAccessibilityEventUnchecked(ev); 425 } 426 return true; 427 } 428 return false; 429 } 430 431 @Override 432 public void computeScroll() { 433 computeScrollHelper(); 434 } 435 436 @Override 437 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 438 if (!mIsDataReady) { 439 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 440 return; 441 } 442 443 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 444 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 445 if (widthMode != MeasureSpec.EXACTLY) { 446 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 447 } 448 449 /* Allow the height to be set as WRAP_CONTENT. This allows the particular case 450 * of the All apps view on XLarge displays to not take up more space then it needs. Width 451 * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect 452 * each page to have the same width. 453 */ 454 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 455 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 456 int maxChildHeight = 0; 457 458 final int verticalPadding = mPaddingTop + mPaddingBottom; 459 final int horizontalPadding = mPaddingLeft + mPaddingRight; 460 461 462 // The children are given the same width and height as the workspace 463 // unless they were set to WRAP_CONTENT 464 if (DEBUG) Log.d(TAG, "PagedView.onMeasure(): " + widthSize + ", " + heightSize); 465 final int childCount = getChildCount(); 466 for (int i = 0; i < childCount; i++) { 467 // disallowing padding in paged view (just pass 0) 468 final View child = getPageAt(i); 469 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 470 471 int childWidthMode; 472 if (lp.width == LayoutParams.WRAP_CONTENT) { 473 childWidthMode = MeasureSpec.AT_MOST; 474 } else { 475 childWidthMode = MeasureSpec.EXACTLY; 476 } 477 478 int childHeightMode; 479 if (lp.height == LayoutParams.WRAP_CONTENT) { 480 childHeightMode = MeasureSpec.AT_MOST; 481 } else { 482 childHeightMode = MeasureSpec.EXACTLY; 483 } 484 485 final int childWidthMeasureSpec = 486 MeasureSpec.makeMeasureSpec(widthSize - horizontalPadding, childWidthMode); 487 final int childHeightMeasureSpec = 488 MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode); 489 490 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 491 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); 492 if (DEBUG) Log.d(TAG, "\tmeasure-child" + i + ": " + child.getMeasuredWidth() + ", " 493 + child.getMeasuredHeight()); 494 } 495 496 if (heightMode == MeasureSpec.AT_MOST) { 497 heightSize = maxChildHeight + verticalPadding; 498 } 499 500 setMeasuredDimension(widthSize, heightSize); 501 502 // We can't call getChildOffset/getRelativeChildOffset until we set the measured dimensions. 503 // We also wait until we set the measured dimensions before flushing the cache as well, to 504 // ensure that the cache is filled with good values. 505 invalidateCachedOffsets(); 506 updateScrollingIndicatorPosition(); 507 508 if (childCount > 0) { 509 mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1); 510 } else { 511 mMaxScrollX = 0; 512 } 513 } 514 515 protected void scrollToNewPageWithoutMovingPages(int newCurrentPage) { 516 int newX = getChildOffset(newCurrentPage) - getRelativeChildOffset(newCurrentPage); 517 int delta = newX - mScrollX; 518 519 final int pageCount = getChildCount(); 520 for (int i = 0; i < pageCount; i++) { 521 View page = (View) getPageAt(i); 522 page.setX(page.getX() + delta); 523 } 524 setCurrentPage(newCurrentPage); 525 } 526 527 // A layout scale of 1.0f assumes that the pages, in their unshrunken state, have a 528 // scale of 1.0f. A layout scale of 0.8f assumes the pages have a scale of 0.8f, and 529 // tightens the layout accordingly 530 public void setLayoutScale(float childrenScale) { 531 mLayoutScale = childrenScale; 532 invalidateCachedOffsets(); 533 534 // Now we need to do a re-layout, but preserving absolute X and Y coordinates 535 int childCount = getChildCount(); 536 float childrenX[] = new float[childCount]; 537 float childrenY[] = new float[childCount]; 538 for (int i = 0; i < childCount; i++) { 539 final View child = getPageAt(i); 540 childrenX[i] = child.getX(); 541 childrenY[i] = child.getY(); 542 } 543 // Trigger a full re-layout (never just call onLayout directly!) 544 int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY); 545 int heightSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY); 546 requestLayout(); 547 measure(widthSpec, heightSpec); 548 layout(mLeft, mTop, mRight, mBottom); 549 for (int i = 0; i < childCount; i++) { 550 final View child = getPageAt(i); 551 child.setX(childrenX[i]); 552 child.setY(childrenY[i]); 553 } 554 555 // Also, the page offset has changed (since the pages are now smaller); 556 // update the page offset, but again preserving absolute X and Y coordinates 557 scrollToNewPageWithoutMovingPages(mCurrentPage); 558 } 559 560 public void setPageSpacing(int pageSpacing) { 561 mPageSpacing = pageSpacing; 562 invalidateCachedOffsets(); 563 } 564 565 @Override 566 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 567 if (!mIsDataReady) { 568 return; 569 } 570 571 if (DEBUG) Log.d(TAG, "PagedView.onLayout()"); 572 final int verticalPadding = mPaddingTop + mPaddingBottom; 573 final int childCount = getChildCount(); 574 int childLeft = 0; 575 if (childCount > 0) { 576 if (DEBUG) Log.d(TAG, "getRelativeChildOffset(): " + getMeasuredWidth() + ", " 577 + getChildWidth(0)); 578 childLeft = getRelativeChildOffset(0); 579 580 // Calculate the variable page spacing if necessary 581 if (mPageSpacing < 0) { 582 setPageSpacing(((right - left) - getChildAt(0).getMeasuredWidth()) / 2); 583 } 584 } 585 586 for (int i = 0; i < childCount; i++) { 587 final View child = getPageAt(i); 588 if (child.getVisibility() != View.GONE) { 589 final int childWidth = getScaledMeasuredWidth(child); 590 final int childHeight = child.getMeasuredHeight(); 591 int childTop = mPaddingTop; 592 if (mCenterPagesVertically) { 593 childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; 594 } 595 596 if (DEBUG) Log.d(TAG, "\tlayout-child" + i + ": " + childLeft + ", " + childTop); 597 child.layout(childLeft, childTop, 598 childLeft + child.getMeasuredWidth(), childTop + childHeight); 599 childLeft += childWidth + mPageSpacing; 600 } 601 } 602 603 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 604 setHorizontalScrollBarEnabled(false); 605 int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage); 606 scrollTo(newX, 0); 607 mScroller.setFinalX(newX); 608 setHorizontalScrollBarEnabled(true); 609 mFirstLayout = false; 610 } 611 612 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 613 mFirstLayout = false; 614 } 615 } 616 617 protected void screenScrolled(int screenCenter) { 618 if (isScrollingIndicatorEnabled()) { 619 updateScrollingIndicator(); 620 } 621 if (mFadeInAdjacentScreens) { 622 for (int i = 0; i < getChildCount(); i++) { 623 View child = getChildAt(i); 624 if (child != null) { 625 float scrollProgress = getScrollProgress(screenCenter, child, i); 626 float alpha = 1 - Math.abs(scrollProgress); 627 child.setFastAlpha(alpha); 628 child.fastInvalidate(); 629 } 630 } 631 invalidate(); 632 } 633 } 634 635 @Override 636 protected void onViewAdded(View child) { 637 super.onViewAdded(child); 638 639 // This ensures that when children are added, they get the correct transforms / alphas 640 // in accordance with any scroll effects. 641 mForceScreenScrolled = true; 642 invalidate(); 643 invalidateCachedOffsets(); 644 } 645 646 protected void invalidateCachedOffsets() { 647 int count = getChildCount(); 648 if (count == 0) { 649 mChildOffsets = null; 650 mChildRelativeOffsets = null; 651 mChildOffsetsWithLayoutScale = null; 652 return; 653 } 654 655 mChildOffsets = new int[count]; 656 mChildRelativeOffsets = new int[count]; 657 mChildOffsetsWithLayoutScale = new int[count]; 658 for (int i = 0; i < count; i++) { 659 mChildOffsets[i] = -1; 660 mChildRelativeOffsets[i] = -1; 661 mChildOffsetsWithLayoutScale[i] = -1; 662 } 663 } 664 665 protected int getChildOffset(int index) { 666 int[] childOffsets = Float.compare(mLayoutScale, 1f) == 0 ? 667 mChildOffsets : mChildOffsetsWithLayoutScale; 668 669 if (childOffsets != null && childOffsets[index] != -1) { 670 return childOffsets[index]; 671 } else { 672 if (getChildCount() == 0) 673 return 0; 674 675 int offset = getRelativeChildOffset(0); 676 for (int i = 0; i < index; ++i) { 677 offset += getScaledMeasuredWidth(getPageAt(i)) + mPageSpacing; 678 } 679 if (childOffsets != null) { 680 childOffsets[index] = offset; 681 } 682 return offset; 683 } 684 } 685 686 protected int getRelativeChildOffset(int index) { 687 if (mChildRelativeOffsets != null && mChildRelativeOffsets[index] != -1) { 688 return mChildRelativeOffsets[index]; 689 } else { 690 final int padding = mPaddingLeft + mPaddingRight; 691 final int offset = mPaddingLeft + 692 (getMeasuredWidth() - padding - getChildWidth(index)) / 2; 693 if (mChildRelativeOffsets != null) { 694 mChildRelativeOffsets[index] = offset; 695 } 696 return offset; 697 } 698 } 699 700 protected int getScaledRelativeChildOffset(int index) { 701 final int padding = mPaddingLeft + mPaddingRight; 702 final int offset = mPaddingLeft + (getMeasuredWidth() - padding - 703 getScaledMeasuredWidth(getPageAt(index))) / 2; 704 return offset; 705 } 706 707 protected int getScaledMeasuredWidth(View child) { 708 // This functions are called enough times that it actually makes a difference in the 709 // profiler -- so just inline the max() here 710 final int measuredWidth = child.getMeasuredWidth(); 711 final int minWidth = mMinimumWidth; 712 final int maxWidth = (minWidth > measuredWidth) ? minWidth : measuredWidth; 713 return (int) (maxWidth * mLayoutScale + 0.5f); 714 } 715 716 protected void getVisiblePages(int[] range) { 717 final int pageCount = getChildCount(); 718 if (pageCount > 0) { 719 final int pageWidth = getScaledMeasuredWidth(getPageAt(0)); 720 final int screenWidth = getMeasuredWidth(); 721 int x = getScaledRelativeChildOffset(0) + pageWidth; 722 int leftScreen = 0; 723 int rightScreen = 0; 724 while (x <= mScrollX && leftScreen < pageCount - 1) { 725 leftScreen++; 726 x += getScaledMeasuredWidth(getPageAt(leftScreen)) + mPageSpacing; 727 } 728 rightScreen = leftScreen; 729 while (x < mScrollX + screenWidth && rightScreen < pageCount - 1) { 730 rightScreen++; 731 x += getScaledMeasuredWidth(getPageAt(rightScreen)) + mPageSpacing; 732 } 733 range[0] = leftScreen; 734 range[1] = rightScreen; 735 } else { 736 range[0] = -1; 737 range[1] = -1; 738 } 739 } 740 741 @Override 742 protected void dispatchDraw(Canvas canvas) { 743 int halfScreenSize = getMeasuredWidth() / 2; 744 // mOverScrollX is equal to mScrollX when we're within the normal scroll range. Otherwise 745 // it is equal to the scaled overscroll position. 746 int screenCenter = mOverScrollX + halfScreenSize; 747 748 if (screenCenter != mLastScreenCenter || mForceScreenScrolled) { 749 screenScrolled(screenCenter); 750 mLastScreenCenter = screenCenter; 751 mForceScreenScrolled = false; 752 } 753 754 // Find out which screens are visible; as an optimization we only call draw on them 755 final int pageCount = getChildCount(); 756 if (pageCount > 0) { 757 getVisiblePages(mTempVisiblePagesRange); 758 final int leftScreen = mTempVisiblePagesRange[0]; 759 final int rightScreen = mTempVisiblePagesRange[1]; 760 if (leftScreen != -1 && rightScreen != -1) { 761 final long drawingTime = getDrawingTime(); 762 // Clip to the bounds 763 canvas.save(); 764 canvas.clipRect(mScrollX, mScrollY, mScrollX + mRight - mLeft, 765 mScrollY + mBottom - mTop); 766 767 for (int i = rightScreen; i >= leftScreen; i--) { 768 drawChild(canvas, getPageAt(i), drawingTime); 769 } 770 canvas.restore(); 771 } 772 } 773 } 774 775 @Override 776 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 777 int page = indexToPage(indexOfChild(child)); 778 if (page != mCurrentPage || !mScroller.isFinished()) { 779 snapToPage(page); 780 return true; 781 } 782 return false; 783 } 784 785 @Override 786 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 787 int focusablePage; 788 if (mNextPage != INVALID_PAGE) { 789 focusablePage = mNextPage; 790 } else { 791 focusablePage = mCurrentPage; 792 } 793 View v = getPageAt(focusablePage); 794 if (v != null) { 795 return v.requestFocus(direction, previouslyFocusedRect); 796 } 797 return false; 798 } 799 800 @Override 801 public boolean dispatchUnhandledMove(View focused, int direction) { 802 if (direction == View.FOCUS_LEFT) { 803 if (getCurrentPage() > 0) { 804 snapToPage(getCurrentPage() - 1); 805 return true; 806 } 807 } else if (direction == View.FOCUS_RIGHT) { 808 if (getCurrentPage() < getPageCount() - 1) { 809 snapToPage(getCurrentPage() + 1); 810 return true; 811 } 812 } 813 return super.dispatchUnhandledMove(focused, direction); 814 } 815 816 @Override 817 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 818 if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { 819 getPageAt(mCurrentPage).addFocusables(views, direction); 820 } 821 if (direction == View.FOCUS_LEFT) { 822 if (mCurrentPage > 0) { 823 getPageAt(mCurrentPage - 1).addFocusables(views, direction); 824 } 825 } else if (direction == View.FOCUS_RIGHT){ 826 if (mCurrentPage < getPageCount() - 1) { 827 getPageAt(mCurrentPage + 1).addFocusables(views, direction); 828 } 829 } 830 } 831 832 /** 833 * If one of our descendant views decides that it could be focused now, only 834 * pass that along if it's on the current page. 835 * 836 * This happens when live folders requery, and if they're off page, they 837 * end up calling requestFocus, which pulls it on page. 838 */ 839 @Override 840 public void focusableViewAvailable(View focused) { 841 View current = getPageAt(mCurrentPage); 842 View v = focused; 843 while (true) { 844 if (v == current) { 845 super.focusableViewAvailable(focused); 846 return; 847 } 848 if (v == this) { 849 return; 850 } 851 ViewParent parent = v.getParent(); 852 if (parent instanceof View) { 853 v = (View)v.getParent(); 854 } else { 855 return; 856 } 857 } 858 } 859 860 /** 861 * {@inheritDoc} 862 */ 863 @Override 864 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 865 if (disallowIntercept) { 866 // We need to make sure to cancel our long press if 867 // a scrollable widget takes over touch events 868 final View currentPage = getPageAt(mCurrentPage); 869 currentPage.cancelLongPress(); 870 } 871 super.requestDisallowInterceptTouchEvent(disallowIntercept); 872 } 873 874 /** 875 * Return true if a tap at (x, y) should trigger a flip to the previous page. 876 */ 877 protected boolean hitsPreviousPage(float x, float y) { 878 return (x < getRelativeChildOffset(mCurrentPage) - mPageSpacing); 879 } 880 881 /** 882 * Return true if a tap at (x, y) should trigger a flip to the next page. 883 */ 884 protected boolean hitsNextPage(float x, float y) { 885 return (x > (getMeasuredWidth() - getRelativeChildOffset(mCurrentPage) + mPageSpacing)); 886 } 887 888 @Override 889 public boolean onInterceptTouchEvent(MotionEvent ev) { 890 /* 891 * This method JUST determines whether we want to intercept the motion. 892 * If we return true, onTouchEvent will be called and we do the actual 893 * scrolling there. 894 */ 895 acquireVelocityTrackerAndAddMovement(ev); 896 897 // Skip touch handling if there are no pages to swipe 898 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); 899 900 /* 901 * Shortcut the most recurring case: the user is in the dragging 902 * state and he is moving his finger. We want to intercept this 903 * motion. 904 */ 905 final int action = ev.getAction(); 906 if ((action == MotionEvent.ACTION_MOVE) && 907 (mTouchState == TOUCH_STATE_SCROLLING)) { 908 return true; 909 } 910 911 switch (action & MotionEvent.ACTION_MASK) { 912 case MotionEvent.ACTION_MOVE: { 913 /* 914 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 915 * whether the user has moved far enough from his original down touch. 916 */ 917 if (mActivePointerId != INVALID_POINTER) { 918 determineScrollingStart(ev); 919 break; 920 } 921 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN 922 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN 923 // i.e. fall through to the next case (don't break) 924 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events 925 // while it's small- this was causing a crash before we checked for INVALID_POINTER) 926 } 927 928 case MotionEvent.ACTION_DOWN: { 929 final float x = ev.getX(); 930 final float y = ev.getY(); 931 // Remember location of down touch 932 mDownMotionX = x; 933 mLastMotionX = x; 934 mLastMotionY = y; 935 mLastMotionXRemainder = 0; 936 mTotalMotionX = 0; 937 mActivePointerId = ev.getPointerId(0); 938 mAllowLongPress = true; 939 940 /* 941 * If being flinged and user touches the screen, initiate drag; 942 * otherwise don't. mScroller.isFinished should be false when 943 * being flinged. 944 */ 945 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); 946 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop); 947 if (finishedScrolling) { 948 mTouchState = TOUCH_STATE_REST; 949 mScroller.abortAnimation(); 950 } else { 951 mTouchState = TOUCH_STATE_SCROLLING; 952 } 953 954 // check if this can be the beginning of a tap on the side of the pages 955 // to scroll the current page 956 if (mTouchState != TOUCH_STATE_PREV_PAGE && mTouchState != TOUCH_STATE_NEXT_PAGE) { 957 if (getChildCount() > 0) { 958 if (hitsPreviousPage(x, y)) { 959 mTouchState = TOUCH_STATE_PREV_PAGE; 960 } else if (hitsNextPage(x, y)) { 961 mTouchState = TOUCH_STATE_NEXT_PAGE; 962 } 963 } 964 } 965 break; 966 } 967 968 case MotionEvent.ACTION_UP: 969 case MotionEvent.ACTION_CANCEL: 970 mTouchState = TOUCH_STATE_REST; 971 mAllowLongPress = false; 972 mActivePointerId = INVALID_POINTER; 973 releaseVelocityTracker(); 974 break; 975 976 case MotionEvent.ACTION_POINTER_UP: 977 onSecondaryPointerUp(ev); 978 releaseVelocityTracker(); 979 break; 980 } 981 982 /* 983 * The only time we want to intercept motion events is if we are in the 984 * drag mode. 985 */ 986 return mTouchState != TOUCH_STATE_REST; 987 } 988 989 protected void animateClickFeedback(View v, final Runnable r) { 990 // animate the view slightly to show click feedback running some logic after it is "pressed" 991 ObjectAnimator anim = (ObjectAnimator) AnimatorInflater. 992 loadAnimator(mContext, R.anim.paged_view_click_feedback); 993 anim.setTarget(v); 994 anim.addListener(new AnimatorListenerAdapter() { 995 public void onAnimationRepeat(Animator animation) { 996 r.run(); 997 } 998 }); 999 anim.start(); 1000 } 1001 1002 protected void determineScrollingStart(MotionEvent ev) { 1003 determineScrollingStart(ev, 1.0f); 1004 } 1005 1006 /* 1007 * Determines if we should change the touch state to start scrolling after the 1008 * user moves their touch point too far. 1009 */ 1010 protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { 1011 /* 1012 * Locally do absolute value. mLastMotionX is set to the y value 1013 * of the down event. 1014 */ 1015 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 1016 if (pointerIndex == -1) { 1017 return; 1018 } 1019 final float x = ev.getX(pointerIndex); 1020 final float y = ev.getY(pointerIndex); 1021 final int xDiff = (int) Math.abs(x - mLastMotionX); 1022 final int yDiff = (int) Math.abs(y - mLastMotionY); 1023 1024 final int touchSlop = Math.round(touchSlopScale * mTouchSlop); 1025 boolean xPaged = xDiff > mPagingTouchSlop; 1026 boolean xMoved = xDiff > touchSlop; 1027 boolean yMoved = yDiff > touchSlop; 1028 1029 if (xMoved || xPaged || yMoved) { 1030 if (mUsePagingTouchSlop ? xPaged : xMoved) { 1031 // Scroll if the user moved far enough along the X axis 1032 mTouchState = TOUCH_STATE_SCROLLING; 1033 mTotalMotionX += Math.abs(mLastMotionX - x); 1034 mLastMotionX = x; 1035 mLastMotionXRemainder = 0; 1036 mTouchX = mScrollX; 1037 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 1038 pageBeginMoving(); 1039 } 1040 // Either way, cancel any pending longpress 1041 cancelCurrentPageLongPress(); 1042 } 1043 } 1044 1045 protected void cancelCurrentPageLongPress() { 1046 if (mAllowLongPress) { 1047 mAllowLongPress = false; 1048 // Try canceling the long press. It could also have been scheduled 1049 // by a distant descendant, so use the mAllowLongPress flag to block 1050 // everything 1051 final View currentPage = getPageAt(mCurrentPage); 1052 if (currentPage != null) { 1053 currentPage.cancelLongPress(); 1054 } 1055 } 1056 } 1057 1058 protected float getScrollProgress(int screenCenter, View v, int page) { 1059 final int halfScreenSize = getMeasuredWidth() / 2; 1060 1061 int totalDistance = getScaledMeasuredWidth(v) + mPageSpacing; 1062 int delta = screenCenter - (getChildOffset(page) - 1063 getRelativeChildOffset(page) + halfScreenSize); 1064 1065 float scrollProgress = delta / (totalDistance * 1.0f); 1066 scrollProgress = Math.min(scrollProgress, 1.0f); 1067 scrollProgress = Math.max(scrollProgress, -1.0f); 1068 return scrollProgress; 1069 } 1070 1071 // This curve determines how the effect of scrolling over the limits of the page dimishes 1072 // as the user pulls further and further from the bounds 1073 private float overScrollInfluenceCurve(float f) { 1074 f -= 1.0f; 1075 return f * f * f + 1.0f; 1076 } 1077 1078 protected void acceleratedOverScroll(float amount) { 1079 int screenSize = getMeasuredWidth(); 1080 1081 // We want to reach the max over scroll effect when the user has 1082 // over scrolled half the size of the screen 1083 float f = OVERSCROLL_ACCELERATE_FACTOR * (amount / screenSize); 1084 1085 if (f == 0) return; 1086 1087 // Clamp this factor, f, to -1 < f < 1 1088 if (Math.abs(f) >= 1) { 1089 f /= Math.abs(f); 1090 } 1091 1092 int overScrollAmount = (int) Math.round(f * screenSize); 1093 if (amount < 0) { 1094 mOverScrollX = overScrollAmount; 1095 mScrollX = 0; 1096 } else { 1097 mOverScrollX = mMaxScrollX + overScrollAmount; 1098 mScrollX = mMaxScrollX; 1099 } 1100 invalidate(); 1101 } 1102 1103 protected void dampedOverScroll(float amount) { 1104 int screenSize = getMeasuredWidth(); 1105 1106 float f = (amount / screenSize); 1107 1108 if (f == 0) return; 1109 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); 1110 1111 // Clamp this factor, f, to -1 < f < 1 1112 if (Math.abs(f) >= 1) { 1113 f /= Math.abs(f); 1114 } 1115 1116 int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); 1117 if (amount < 0) { 1118 mOverScrollX = overScrollAmount; 1119 mScrollX = 0; 1120 } else { 1121 mOverScrollX = mMaxScrollX + overScrollAmount; 1122 mScrollX = mMaxScrollX; 1123 } 1124 invalidate(); 1125 } 1126 1127 protected void overScroll(float amount) { 1128 dampedOverScroll(amount); 1129 } 1130 1131 protected float maxOverScroll() { 1132 // Using the formula in overScroll, assuming that f = 1.0 (which it should generally not 1133 // exceed). Used to find out how much extra wallpaper we need for the over scroll effect 1134 float f = 1.0f; 1135 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); 1136 return OVERSCROLL_DAMP_FACTOR * f; 1137 } 1138 1139 @Override 1140 public boolean onTouchEvent(MotionEvent ev) { 1141 // Skip touch handling if there are no pages to swipe 1142 if (getChildCount() <= 0) return super.onTouchEvent(ev); 1143 1144 acquireVelocityTrackerAndAddMovement(ev); 1145 1146 final int action = ev.getAction(); 1147 1148 switch (action & MotionEvent.ACTION_MASK) { 1149 case MotionEvent.ACTION_DOWN: 1150 /* 1151 * If being flinged and user touches, stop the fling. isFinished 1152 * will be false if being flinged. 1153 */ 1154 if (!mScroller.isFinished()) { 1155 mScroller.abortAnimation(); 1156 } 1157 1158 // Remember where the motion event started 1159 mDownMotionX = mLastMotionX = ev.getX(); 1160 mLastMotionXRemainder = 0; 1161 mTotalMotionX = 0; 1162 mActivePointerId = ev.getPointerId(0); 1163 if (mTouchState == TOUCH_STATE_SCROLLING) { 1164 pageBeginMoving(); 1165 } 1166 break; 1167 1168 case MotionEvent.ACTION_MOVE: 1169 if (mTouchState == TOUCH_STATE_SCROLLING) { 1170 // Scroll to follow the motion event 1171 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 1172 final float x = ev.getX(pointerIndex); 1173 final float deltaX = mLastMotionX + mLastMotionXRemainder - x; 1174 1175 mTotalMotionX += Math.abs(deltaX); 1176 1177 // Only scroll and update mLastMotionX if we have moved some discrete amount. We 1178 // keep the remainder because we are actually testing if we've moved from the last 1179 // scrolled position (which is discrete). 1180 if (Math.abs(deltaX) >= 1.0f) { 1181 mTouchX += deltaX; 1182 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 1183 if (!mDeferScrollUpdate) { 1184 scrollBy((int) deltaX, 0); 1185 if (DEBUG) Log.d(TAG, "onTouchEvent().Scrolling: " + deltaX); 1186 } else { 1187 invalidate(); 1188 } 1189 mLastMotionX = x; 1190 mLastMotionXRemainder = deltaX - (int) deltaX; 1191 } else { 1192 awakenScrollBars(); 1193 } 1194 } else { 1195 determineScrollingStart(ev); 1196 } 1197 break; 1198 1199 case MotionEvent.ACTION_UP: 1200 if (mTouchState == TOUCH_STATE_SCROLLING) { 1201 final int activePointerId = mActivePointerId; 1202 final int pointerIndex = ev.findPointerIndex(activePointerId); 1203 final float x = ev.getX(pointerIndex); 1204 final VelocityTracker velocityTracker = mVelocityTracker; 1205 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1206 int velocityX = (int) velocityTracker.getXVelocity(activePointerId); 1207 final int deltaX = (int) (x - mDownMotionX); 1208 final int pageWidth = getScaledMeasuredWidth(getPageAt(mCurrentPage)); 1209 boolean isSignificantMove = Math.abs(deltaX) > pageWidth * 1210 SIGNIFICANT_MOVE_THRESHOLD; 1211 final int snapVelocity = mSnapVelocity; 1212 1213 mTotalMotionX += Math.abs(mLastMotionX + mLastMotionXRemainder - x); 1214 1215 boolean isFling = mTotalMotionX > MIN_LENGTH_FOR_FLING && 1216 Math.abs(velocityX) > snapVelocity; 1217 1218 // In the case that the page is moved far to one direction and then is flung 1219 // in the opposite direction, we use a threshold to determine whether we should 1220 // just return to the starting page, or if we should skip one further. 1221 boolean returnToOriginalPage = false; 1222 if (Math.abs(deltaX) > pageWidth * RETURN_TO_ORIGINAL_PAGE_THRESHOLD && 1223 Math.signum(velocityX) != Math.signum(deltaX) && isFling) { 1224 returnToOriginalPage = true; 1225 } 1226 1227 int finalPage; 1228 // We give flings precedence over large moves, which is why we short-circuit our 1229 // test for a large move if a fling has been registered. That is, a large 1230 // move to the left and fling to the right will register as a fling to the right. 1231 if (((isSignificantMove && deltaX > 0 && !isFling) || 1232 (isFling && velocityX > 0)) && mCurrentPage > 0) { 1233 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage - 1; 1234 snapToPageWithVelocity(finalPage, velocityX); 1235 } else if (((isSignificantMove && deltaX < 0 && !isFling) || 1236 (isFling && velocityX < 0)) && 1237 mCurrentPage < getChildCount() - 1) { 1238 finalPage = returnToOriginalPage ? mCurrentPage : mCurrentPage + 1; 1239 snapToPageWithVelocity(finalPage, velocityX); 1240 } else { 1241 snapToDestination(); 1242 } 1243 } else if (mTouchState == TOUCH_STATE_PREV_PAGE) { 1244 // at this point we have not moved beyond the touch slop 1245 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 1246 // we can just page 1247 int nextPage = Math.max(0, mCurrentPage - 1); 1248 if (nextPage != mCurrentPage) { 1249 snapToPage(nextPage); 1250 } else { 1251 snapToDestination(); 1252 } 1253 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE) { 1254 // at this point we have not moved beyond the touch slop 1255 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 1256 // we can just page 1257 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); 1258 if (nextPage != mCurrentPage) { 1259 snapToPage(nextPage); 1260 } else { 1261 snapToDestination(); 1262 } 1263 } else { 1264 onUnhandledTap(ev); 1265 } 1266 mTouchState = TOUCH_STATE_REST; 1267 mActivePointerId = INVALID_POINTER; 1268 releaseVelocityTracker(); 1269 break; 1270 1271 case MotionEvent.ACTION_CANCEL: 1272 if (mTouchState == TOUCH_STATE_SCROLLING) { 1273 snapToDestination(); 1274 } 1275 mTouchState = TOUCH_STATE_REST; 1276 mActivePointerId = INVALID_POINTER; 1277 releaseVelocityTracker(); 1278 break; 1279 1280 case MotionEvent.ACTION_POINTER_UP: 1281 onSecondaryPointerUp(ev); 1282 break; 1283 } 1284 1285 return true; 1286 } 1287 1288 @Override 1289 public boolean onGenericMotionEvent(MotionEvent event) { 1290 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 1291 switch (event.getAction()) { 1292 case MotionEvent.ACTION_SCROLL: { 1293 // Handle mouse (or ext. device) by shifting the page depending on the scroll 1294 final float vscroll; 1295 final float hscroll; 1296 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { 1297 vscroll = 0; 1298 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 1299 } else { 1300 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); 1301 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 1302 } 1303 if (hscroll != 0 || vscroll != 0) { 1304 if (hscroll > 0 || vscroll > 0) { 1305 scrollRight(); 1306 } else { 1307 scrollLeft(); 1308 } 1309 return true; 1310 } 1311 } 1312 } 1313 } 1314 return super.onGenericMotionEvent(event); 1315 } 1316 1317 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { 1318 if (mVelocityTracker == null) { 1319 mVelocityTracker = VelocityTracker.obtain(); 1320 } 1321 mVelocityTracker.addMovement(ev); 1322 } 1323 1324 private void releaseVelocityTracker() { 1325 if (mVelocityTracker != null) { 1326 mVelocityTracker.recycle(); 1327 mVelocityTracker = null; 1328 } 1329 } 1330 1331 private void onSecondaryPointerUp(MotionEvent ev) { 1332 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 1333 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 1334 final int pointerId = ev.getPointerId(pointerIndex); 1335 if (pointerId == mActivePointerId) { 1336 // This was our active pointer going up. Choose a new 1337 // active pointer and adjust accordingly. 1338 // TODO: Make this decision more intelligent. 1339 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1340 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); 1341 mLastMotionY = ev.getY(newPointerIndex); 1342 mLastMotionXRemainder = 0; 1343 mActivePointerId = ev.getPointerId(newPointerIndex); 1344 if (mVelocityTracker != null) { 1345 mVelocityTracker.clear(); 1346 } 1347 } 1348 } 1349 1350 protected void onUnhandledTap(MotionEvent ev) {} 1351 1352 @Override 1353 public void requestChildFocus(View child, View focused) { 1354 super.requestChildFocus(child, focused); 1355 int page = indexToPage(indexOfChild(child)); 1356 if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) { 1357 snapToPage(page); 1358 } 1359 } 1360 1361 protected int getChildIndexForRelativeOffset(int relativeOffset) { 1362 final int childCount = getChildCount(); 1363 int left; 1364 int right; 1365 for (int i = 0; i < childCount; ++i) { 1366 left = getRelativeChildOffset(i); 1367 right = (left + getScaledMeasuredWidth(getPageAt(i))); 1368 if (left <= relativeOffset && relativeOffset <= right) { 1369 return i; 1370 } 1371 } 1372 return -1; 1373 } 1374 1375 protected int getChildWidth(int index) { 1376 // This functions are called enough times that it actually makes a difference in the 1377 // profiler -- so just inline the max() here 1378 final int measuredWidth = getPageAt(index).getMeasuredWidth(); 1379 final int minWidth = mMinimumWidth; 1380 return (minWidth > measuredWidth) ? minWidth : measuredWidth; 1381 } 1382 1383 int getPageNearestToCenterOfScreen() { 1384 int minDistanceFromScreenCenter = Integer.MAX_VALUE; 1385 int minDistanceFromScreenCenterIndex = -1; 1386 int screenCenter = mScrollX + (getMeasuredWidth() / 2); 1387 final int childCount = getChildCount(); 1388 for (int i = 0; i < childCount; ++i) { 1389 View layout = (View) getPageAt(i); 1390 int childWidth = getScaledMeasuredWidth(layout); 1391 int halfChildWidth = (childWidth / 2); 1392 int childCenter = getChildOffset(i) + halfChildWidth; 1393 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); 1394 if (distanceFromScreenCenter < minDistanceFromScreenCenter) { 1395 minDistanceFromScreenCenter = distanceFromScreenCenter; 1396 minDistanceFromScreenCenterIndex = i; 1397 } 1398 } 1399 return minDistanceFromScreenCenterIndex; 1400 } 1401 1402 protected void snapToDestination() { 1403 snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); 1404 } 1405 1406 private static class ScrollInterpolator implements Interpolator { 1407 public ScrollInterpolator() { 1408 } 1409 1410 public float getInterpolation(float t) { 1411 t -= 1.0f; 1412 return t*t*t*t*t + 1; 1413 } 1414 } 1415 1416 // We want the duration of the page snap animation to be influenced by the distance that 1417 // the screen has to travel, however, we don't want this duration to be effected in a 1418 // purely linear fashion. Instead, we use this method to moderate the effect that the distance 1419 // of travel has on the overall snap duration. 1420 float distanceInfluenceForSnapDuration(float f) { 1421 f -= 0.5f; // center the values about 0. 1422 f *= 0.3f * Math.PI / 2.0f; 1423 return (float) Math.sin(f); 1424 } 1425 1426 protected void snapToPageWithVelocity(int whichPage, int velocity) { 1427 whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); 1428 int halfScreenSize = getMeasuredWidth() / 2; 1429 1430 if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); 1431 if (DEBUG) Log.d(TAG, "snapToPageWithVelocity.getRelativeChildOffset(): " 1432 + getMeasuredWidth() + ", " + getChildWidth(whichPage)); 1433 final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); 1434 int delta = newX - mUnboundedScrollX; 1435 int duration = 0; 1436 1437 if (Math.abs(velocity) < MIN_FLING_VELOCITY) { 1438 // If the velocity is low enough, then treat this more as an automatic page advance 1439 // as opposed to an apparent physical response to flinging 1440 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); 1441 return; 1442 } 1443 1444 // Here we compute a "distance" that will be used in the computation of the overall 1445 // snap duration. This is a function of the actual distance that needs to be traveled; 1446 // we keep this value close to half screen size in order to reduce the variance in snap 1447 // duration as a function of the distance the page needs to travel. 1448 float distanceRatio = Math.min(1f, 1.0f * Math.abs(delta) / (2 * halfScreenSize)); 1449 float distance = halfScreenSize + halfScreenSize * 1450 distanceInfluenceForSnapDuration(distanceRatio); 1451 1452 velocity = Math.abs(velocity); 1453 velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity); 1454 1455 // we want the page's snap velocity to approximately match the velocity at which the 1456 // user flings, so we scale the duration by a value near to the derivative of the scroll 1457 // interpolator at zero, ie. 5. We use 4 to make it a little slower. 1458 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 1459 1460 snapToPage(whichPage, delta, duration); 1461 } 1462 1463 protected void snapToPage(int whichPage) { 1464 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); 1465 } 1466 1467 protected void snapToPage(int whichPage, int duration) { 1468 whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1)); 1469 1470 if (DEBUG) Log.d(TAG, "snapToPage.getChildOffset(): " + getChildOffset(whichPage)); 1471 if (DEBUG) Log.d(TAG, "snapToPage.getRelativeChildOffset(): " + getMeasuredWidth() + ", " 1472 + getChildWidth(whichPage)); 1473 int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); 1474 final int sX = mUnboundedScrollX; 1475 final int delta = newX - sX; 1476 snapToPage(whichPage, delta, duration); 1477 } 1478 1479 protected void snapToPage(int whichPage, int delta, int duration) { 1480 mNextPage = whichPage; 1481 1482 View focusedChild = getFocusedChild(); 1483 if (focusedChild != null && whichPage != mCurrentPage && 1484 focusedChild == getPageAt(mCurrentPage)) { 1485 focusedChild.clearFocus(); 1486 } 1487 1488 pageBeginMoving(); 1489 awakenScrollBars(duration); 1490 if (duration == 0) { 1491 duration = Math.abs(delta); 1492 } 1493 1494 if (!mScroller.isFinished()) mScroller.abortAnimation(); 1495 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); 1496 1497 // Load associated pages immediately if someone else is handling the scroll, otherwise defer 1498 // loading associated pages until the scroll settles 1499 if (mDeferScrollUpdate) { 1500 loadAssociatedPages(mNextPage); 1501 } else { 1502 mDeferLoadAssociatedPagesUntilScrollCompletes = true; 1503 } 1504 notifyPageSwitchListener(); 1505 invalidate(); 1506 } 1507 1508 public void scrollLeft() { 1509 if (mScroller.isFinished()) { 1510 if (mCurrentPage > 0) snapToPage(mCurrentPage - 1); 1511 } else { 1512 if (mNextPage > 0) snapToPage(mNextPage - 1); 1513 } 1514 } 1515 1516 public void scrollRight() { 1517 if (mScroller.isFinished()) { 1518 if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1); 1519 } else { 1520 if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1); 1521 } 1522 } 1523 1524 public int getPageForView(View v) { 1525 int result = -1; 1526 if (v != null) { 1527 ViewParent vp = v.getParent(); 1528 int count = getChildCount(); 1529 for (int i = 0; i < count; i++) { 1530 if (vp == getPageAt(i)) { 1531 return i; 1532 } 1533 } 1534 } 1535 return result; 1536 } 1537 1538 /** 1539 * @return True is long presses are still allowed for the current touch 1540 */ 1541 public boolean allowLongPress() { 1542 return mAllowLongPress; 1543 } 1544 1545 /** 1546 * Set true to allow long-press events to be triggered, usually checked by 1547 * {@link Launcher} to accept or block dpad-initiated long-presses. 1548 */ 1549 public void setAllowLongPress(boolean allowLongPress) { 1550 mAllowLongPress = allowLongPress; 1551 } 1552 1553 public static class SavedState extends BaseSavedState { 1554 int currentPage = -1; 1555 1556 SavedState(Parcelable superState) { 1557 super(superState); 1558 } 1559 1560 private SavedState(Parcel in) { 1561 super(in); 1562 currentPage = in.readInt(); 1563 } 1564 1565 @Override 1566 public void writeToParcel(Parcel out, int flags) { 1567 super.writeToParcel(out, flags); 1568 out.writeInt(currentPage); 1569 } 1570 1571 public static final Parcelable.Creator<SavedState> CREATOR = 1572 new Parcelable.Creator<SavedState>() { 1573 public SavedState createFromParcel(Parcel in) { 1574 return new SavedState(in); 1575 } 1576 1577 public SavedState[] newArray(int size) { 1578 return new SavedState[size]; 1579 } 1580 }; 1581 } 1582 1583 protected void loadAssociatedPages(int page) { 1584 loadAssociatedPages(page, false); 1585 } 1586 protected void loadAssociatedPages(int page, boolean immediateAndOnly) { 1587 if (mContentIsRefreshable) { 1588 final int count = getChildCount(); 1589 if (page < count) { 1590 int lowerPageBound = getAssociatedLowerPageBound(page); 1591 int upperPageBound = getAssociatedUpperPageBound(page); 1592 if (DEBUG) Log.d(TAG, "loadAssociatedPages: " + lowerPageBound + "/" 1593 + upperPageBound); 1594 for (int i = 0; i < count; ++i) { 1595 if ((i != page) && immediateAndOnly) { 1596 continue; 1597 } 1598 Page layout = (Page) getPageAt(i); 1599 final int childCount = layout.getPageChildCount(); 1600 if (lowerPageBound <= i && i <= upperPageBound) { 1601 if (mDirtyPageContent.get(i)) { 1602 syncPageItems(i, (i == page) && immediateAndOnly); 1603 mDirtyPageContent.set(i, false); 1604 } 1605 } else { 1606 if (childCount > 0) { 1607 layout.removeAllViewsOnPage(); 1608 } 1609 mDirtyPageContent.set(i, true); 1610 } 1611 } 1612 } 1613 } 1614 } 1615 1616 protected int getAssociatedLowerPageBound(int page) { 1617 return Math.max(0, page - 1); 1618 } 1619 protected int getAssociatedUpperPageBound(int page) { 1620 final int count = getChildCount(); 1621 return Math.min(page + 1, count - 1); 1622 } 1623 1624 protected void startChoiceMode(int mode, ActionMode.Callback callback) { 1625 if (isChoiceMode(CHOICE_MODE_NONE)) { 1626 mChoiceMode = mode; 1627 mActionMode = startActionMode(callback); 1628 } 1629 } 1630 1631 public void endChoiceMode() { 1632 if (!isChoiceMode(CHOICE_MODE_NONE)) { 1633 mChoiceMode = CHOICE_MODE_NONE; 1634 resetCheckedGrandchildren(); 1635 if (mActionMode != null) mActionMode.finish(); 1636 mActionMode = null; 1637 } 1638 } 1639 1640 protected boolean isChoiceMode(int mode) { 1641 return mChoiceMode == mode; 1642 } 1643 1644 protected ArrayList<Checkable> getCheckedGrandchildren() { 1645 ArrayList<Checkable> checked = new ArrayList<Checkable>(); 1646 final int childCount = getChildCount(); 1647 for (int i = 0; i < childCount; ++i) { 1648 Page layout = (Page) getPageAt(i); 1649 final int grandChildCount = layout.getPageChildCount(); 1650 for (int j = 0; j < grandChildCount; ++j) { 1651 final View v = layout.getChildOnPageAt(j); 1652 if (v instanceof Checkable && ((Checkable) v).isChecked()) { 1653 checked.add((Checkable) v); 1654 } 1655 } 1656 } 1657 return checked; 1658 } 1659 1660 /** 1661 * If in CHOICE_MODE_SINGLE and an item is checked, returns that item. 1662 * Otherwise, returns null. 1663 */ 1664 protected Checkable getSingleCheckedGrandchild() { 1665 if (mChoiceMode != CHOICE_MODE_MULTIPLE) { 1666 final int childCount = getChildCount(); 1667 for (int i = 0; i < childCount; ++i) { 1668 Page layout = (Page) getPageAt(i); 1669 final int grandChildCount = layout.getPageChildCount(); 1670 for (int j = 0; j < grandChildCount; ++j) { 1671 final View v = layout.getChildOnPageAt(j); 1672 if (v instanceof Checkable && ((Checkable) v).isChecked()) { 1673 return (Checkable) v; 1674 } 1675 } 1676 } 1677 } 1678 return null; 1679 } 1680 1681 protected void resetCheckedGrandchildren() { 1682 // loop through children, and set all of their children to _not_ be checked 1683 final ArrayList<Checkable> checked = getCheckedGrandchildren(); 1684 for (int i = 0; i < checked.size(); ++i) { 1685 final Checkable c = checked.get(i); 1686 c.setChecked(false); 1687 } 1688 } 1689 1690 /** 1691 * This method is called ONLY to synchronize the number of pages that the paged view has. 1692 * To actually fill the pages with information, implement syncPageItems() below. It is 1693 * guaranteed that syncPageItems() will be called for a particular page before it is shown, 1694 * and therefore, individual page items do not need to be updated in this method. 1695 */ 1696 public abstract void syncPages(); 1697 1698 /** 1699 * This method is called to synchronize the items that are on a particular page. If views on 1700 * the page can be reused, then they should be updated within this method. 1701 */ 1702 public abstract void syncPageItems(int page, boolean immediate); 1703 1704 protected void invalidatePageData() { 1705 invalidatePageData(-1, false); 1706 } 1707 protected void invalidatePageData(int currentPage) { 1708 invalidatePageData(currentPage, false); 1709 } 1710 protected void invalidatePageData(int currentPage, boolean immediateAndOnly) { 1711 if (!mIsDataReady) { 1712 return; 1713 } 1714 1715 if (mContentIsRefreshable) { 1716 // Force all scrolling-related behavior to end 1717 mScroller.forceFinished(true); 1718 mNextPage = INVALID_PAGE; 1719 1720 // Update all the pages 1721 syncPages(); 1722 1723 // We must force a measure after we've loaded the pages to update the content width and 1724 // to determine the full scroll width 1725 measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), 1726 MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); 1727 1728 // Set a new page as the current page if necessary 1729 if (currentPage > -1) { 1730 setCurrentPage(Math.min(getPageCount() - 1, currentPage)); 1731 } 1732 1733 // Mark each of the pages as dirty 1734 final int count = getChildCount(); 1735 mDirtyPageContent.clear(); 1736 for (int i = 0; i < count; ++i) { 1737 mDirtyPageContent.add(true); 1738 } 1739 1740 // Load any pages that are necessary for the current window of views 1741 loadAssociatedPages(mCurrentPage, immediateAndOnly); 1742 requestLayout(); 1743 } 1744 } 1745 1746 protected ImageView getScrollingIndicator() { 1747 // We use mHasScrollIndicator to prevent future lookups if there is no sibling indicator 1748 // found 1749 if (mHasScrollIndicator && mScrollIndicator == null) { 1750 ViewGroup parent = (ViewGroup) getParent(); 1751 mScrollIndicator = (ImageView) (parent.findViewById(R.id.paged_view_indicator)); 1752 mHasScrollIndicator = mScrollIndicator != null; 1753 if (mHasScrollIndicator) { 1754 mScrollIndicator.setVisibility(View.VISIBLE); 1755 } 1756 } 1757 return mScrollIndicator; 1758 } 1759 1760 protected boolean isScrollingIndicatorEnabled() { 1761 return !LauncherApplication.isScreenLarge(); 1762 } 1763 1764 Runnable hideScrollingIndicatorRunnable = new Runnable() { 1765 @Override 1766 public void run() { 1767 hideScrollingIndicator(false); 1768 } 1769 }; 1770 protected void flashScrollingIndicator(boolean animated) { 1771 removeCallbacks(hideScrollingIndicatorRunnable); 1772 showScrollingIndicator(!animated); 1773 postDelayed(hideScrollingIndicatorRunnable, sScrollIndicatorFlashDuration); 1774 } 1775 1776 protected void showScrollingIndicator(boolean immediately) { 1777 if (getChildCount() <= 1) return; 1778 if (!isScrollingIndicatorEnabled()) return; 1779 1780 getScrollingIndicator(); 1781 if (mScrollIndicator != null) { 1782 // Fade the indicator in 1783 updateScrollingIndicatorPosition(); 1784 mScrollIndicator.setVisibility(View.VISIBLE); 1785 cancelScrollingIndicatorAnimations(); 1786 if (immediately) { 1787 mScrollIndicator.setAlpha(1f); 1788 } else { 1789 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 1f); 1790 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeInDuration); 1791 mScrollIndicatorAnimator.start(); 1792 } 1793 } 1794 } 1795 1796 protected void cancelScrollingIndicatorAnimations() { 1797 if (mScrollIndicatorAnimator != null) { 1798 mScrollIndicatorAnimator.cancel(); 1799 } 1800 } 1801 1802 protected void hideScrollingIndicator(boolean immediately) { 1803 if (getChildCount() <= 1) return; 1804 if (!isScrollingIndicatorEnabled()) return; 1805 1806 getScrollingIndicator(); 1807 if (mScrollIndicator != null) { 1808 // Fade the indicator out 1809 updateScrollingIndicatorPosition(); 1810 cancelScrollingIndicatorAnimations(); 1811 if (immediately) { 1812 mScrollIndicator.setVisibility(View.INVISIBLE); 1813 mScrollIndicator.setAlpha(0f); 1814 } else { 1815 mScrollIndicatorAnimator = ObjectAnimator.ofFloat(mScrollIndicator, "alpha", 0f); 1816 mScrollIndicatorAnimator.setDuration(sScrollIndicatorFadeOutDuration); 1817 mScrollIndicatorAnimator.addListener(new AnimatorListenerAdapter() { 1818 private boolean cancelled = false; 1819 @Override 1820 public void onAnimationCancel(android.animation.Animator animation) { 1821 cancelled = true; 1822 } 1823 @Override 1824 public void onAnimationEnd(Animator animation) { 1825 if (!cancelled) { 1826 mScrollIndicator.setVisibility(View.INVISIBLE); 1827 } 1828 } 1829 }); 1830 mScrollIndicatorAnimator.start(); 1831 } 1832 } 1833 } 1834 1835 /** 1836 * To be overridden by subclasses to determine whether the scroll indicator should stretch to 1837 * fill its space on the track or not. 1838 */ 1839 protected boolean hasElasticScrollIndicator() { 1840 return true; 1841 } 1842 1843 private void updateScrollingIndicator() { 1844 if (getChildCount() <= 1) return; 1845 if (!isScrollingIndicatorEnabled()) return; 1846 1847 getScrollingIndicator(); 1848 if (mScrollIndicator != null) { 1849 updateScrollingIndicatorPosition(); 1850 } 1851 } 1852 1853 private void updateScrollingIndicatorPosition() { 1854 if (!isScrollingIndicatorEnabled()) return; 1855 if (mScrollIndicator == null) return; 1856 int numPages = getChildCount(); 1857 int pageWidth = getMeasuredWidth(); 1858 int lastChildIndex = Math.max(0, getChildCount() - 1); 1859 int maxScrollX = getChildOffset(lastChildIndex) - getRelativeChildOffset(lastChildIndex); 1860 int trackWidth = pageWidth - mScrollIndicatorPaddingLeft - mScrollIndicatorPaddingRight; 1861 int indicatorWidth = mScrollIndicator.getMeasuredWidth() - 1862 mScrollIndicator.getPaddingLeft() - mScrollIndicator.getPaddingRight(); 1863 1864 float offset = Math.max(0f, Math.min(1f, (float) getScrollX() / maxScrollX)); 1865 int indicatorSpace = trackWidth / numPages; 1866 int indicatorPos = (int) (offset * (trackWidth - indicatorSpace)) + mScrollIndicatorPaddingLeft; 1867 if (hasElasticScrollIndicator()) { 1868 if (mScrollIndicator.getMeasuredWidth() != indicatorSpace) { 1869 mScrollIndicator.getLayoutParams().width = indicatorSpace; 1870 mScrollIndicator.requestLayout(); 1871 } 1872 } else { 1873 int indicatorCenterOffset = indicatorSpace / 2 - indicatorWidth / 2; 1874 indicatorPos += indicatorCenterOffset; 1875 } 1876 mScrollIndicator.setTranslationX(indicatorPos); 1877 mScrollIndicator.invalidate(); 1878 } 1879 1880 public void showScrollIndicatorTrack() { 1881 } 1882 1883 public void hideScrollIndicatorTrack() { 1884 } 1885 1886 /* Accessibility */ 1887 @Override 1888 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1889 super.onInitializeAccessibilityNodeInfo(info); 1890 info.setScrollable(true); 1891 } 1892 1893 @Override 1894 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1895 super.onInitializeAccessibilityEvent(event); 1896 event.setScrollable(true); 1897 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 1898 event.setFromIndex(mCurrentPage); 1899 event.setToIndex(mCurrentPage); 1900 event.setItemCount(getChildCount()); 1901 } 1902 } 1903 1904 protected String getCurrentPageDescription() { 1905 int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage; 1906 return String.format(mContext.getString(R.string.default_scroll_format), 1907 page + 1, getChildCount()); 1908 } 1909 1910 @Override 1911 public boolean onHoverEvent(android.view.MotionEvent event) { 1912 return true; 1913 } 1914 } 1915