PagedView.java revision 73aa9755d3db1a76e9de0f55271ef5984d78ef6f
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 17package com.android.launcher2; 18 19import java.util.ArrayList; 20import java.util.HashMap; 21 22import android.content.Context; 23import android.content.res.TypedArray; 24import android.graphics.Bitmap; 25import android.graphics.Canvas; 26import android.graphics.Rect; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.util.AttributeSet; 30import android.view.ActionMode; 31import android.view.MotionEvent; 32import android.view.VelocityTracker; 33import android.view.View; 34import android.view.ViewConfiguration; 35import android.view.ViewGroup; 36import android.view.ViewParent; 37import android.view.animation.Animation; 38import android.view.animation.AnimationUtils; 39import android.view.animation.Interpolator; 40import android.view.animation.Animation.AnimationListener; 41import android.widget.Checkable; 42import android.widget.Scroller; 43 44import com.android.launcher.R; 45 46/** 47 * An abstraction of the original Workspace which supports browsing through a 48 * sequential list of "pages" 49 */ 50public abstract class PagedView extends ViewGroup { 51 private static final String TAG = "PagedView"; 52 protected static final int INVALID_PAGE = -1; 53 54 // the min drag distance for a fling to register, to prevent random page shifts 55 private static final int MIN_LENGTH_FOR_FLING = 25; 56 // The min drag distance to trigger a page shift (regardless of velocity) 57 private static final int MIN_LENGTH_FOR_MOVE = 200; 58 59 private static final int PAGE_SNAP_ANIMATION_DURATION = 550; 60 protected static final float NANOTIME_DIV = 1000000000.0f; 61 62 private static final float OVERSCROLL_DAMP_FACTOR = 0.08f; 63 private static final int MINIMUM_SNAP_VELOCITY = 2200; 64 private static final int MIN_FLING_VELOCITY = 250; 65 66 // the velocity at which a fling gesture will cause us to snap to the next page 67 protected int mSnapVelocity = 500; 68 69 protected float mSmoothingTime; 70 protected float mTouchX; 71 72 protected boolean mFirstLayout = true; 73 74 protected int mCurrentPage; 75 protected int mNextPage = INVALID_PAGE; 76 protected int mMaxScrollX; 77 protected Scroller mScroller; 78 private VelocityTracker mVelocityTracker; 79 80 private float mDownMotionX; 81 protected float mLastMotionX; 82 protected float mLastMotionY; 83 private int mLastScreenCenter = -1; 84 85 protected final static int TOUCH_STATE_REST = 0; 86 protected final static int TOUCH_STATE_SCROLLING = 1; 87 protected final static int TOUCH_STATE_PREV_PAGE = 2; 88 protected final static int TOUCH_STATE_NEXT_PAGE = 3; 89 protected final static float ALPHA_QUANTIZE_LEVEL = 0.0001f; 90 91 protected int mTouchState = TOUCH_STATE_REST; 92 93 protected OnLongClickListener mLongClickListener; 94 95 protected boolean mAllowLongPress = true; 96 97 protected int mTouchSlop; 98 private int mPagingTouchSlop; 99 private int mMaximumVelocity; 100 protected int mPageSpacing; 101 protected int mPageLayoutPaddingTop; 102 protected int mPageLayoutPaddingBottom; 103 protected int mPageLayoutPaddingLeft; 104 protected int mPageLayoutPaddingRight; 105 protected int mPageLayoutWidthGap; 106 protected int mPageLayoutHeightGap; 107 protected int mCellCountX; 108 protected int mCellCountY; 109 protected boolean mCenterPagesVertically; 110 protected boolean mAllowOverScroll = true; 111 protected int mUnboundedScrollX; 112 113 protected static final int INVALID_POINTER = -1; 114 115 protected int mActivePointerId = INVALID_POINTER; 116 117 private PageSwitchListener mPageSwitchListener; 118 119 private ArrayList<Boolean> mDirtyPageContent; 120 private boolean mDirtyPageAlpha; 121 122 // choice modes 123 protected static final int CHOICE_MODE_NONE = 0; 124 protected static final int CHOICE_MODE_SINGLE = 1; 125 // Multiple selection mode is not supported by all Launcher actions atm 126 protected static final int CHOICE_MODE_MULTIPLE = 2; 127 128 protected int mChoiceMode; 129 private ActionMode mActionMode; 130 131 protected PagedViewIconCache mPageViewIconCache; 132 133 // If true, syncPages and syncPageItems will be called to refresh pages 134 protected boolean mContentIsRefreshable = true; 135 136 // If true, modify alpha of neighboring pages as user scrolls left/right 137 protected boolean mFadeInAdjacentScreens = true; 138 139 // It true, use a different slop parameter (pagingTouchSlop = 2 * touchSlop) for deciding 140 // to switch to a new page 141 protected boolean mUsePagingTouchSlop = true; 142 143 // If true, the subclass should directly update mScrollX itself in its computeScroll method 144 // (SmoothPagedView does this) 145 protected boolean mDeferScrollUpdate = false; 146 147 protected boolean mIsPageMoving = false; 148 149 /** 150 * Simple cache mechanism for PagedViewIcon outlines. 151 */ 152 class PagedViewIconCache { 153 private final HashMap<Object, Bitmap> iconOutlineCache = new HashMap<Object, Bitmap>(); 154 155 public void clear() { 156 iconOutlineCache.clear(); 157 } 158 public void addOutline(Object key, Bitmap b) { 159 iconOutlineCache.put(key, b); 160 } 161 public void removeOutline(Object key) { 162 if (iconOutlineCache.containsKey(key)) { 163 iconOutlineCache.remove(key); 164 } 165 } 166 public Bitmap getOutline(Object key) { 167 return iconOutlineCache.get(key); 168 } 169 } 170 171 public interface PageSwitchListener { 172 void onPageSwitch(View newPage, int newPageIndex); 173 } 174 175 public PagedView(Context context) { 176 this(context, null); 177 } 178 179 public PagedView(Context context, AttributeSet attrs) { 180 this(context, attrs, 0); 181 } 182 183 public PagedView(Context context, AttributeSet attrs, int defStyle) { 184 super(context, attrs, defStyle); 185 mChoiceMode = CHOICE_MODE_NONE; 186 187 TypedArray a = context.obtainStyledAttributes(attrs, 188 R.styleable.PagedView, defStyle, 0); 189 mPageSpacing = a.getDimensionPixelSize(R.styleable.PagedView_pageSpacing, 0); 190 mPageLayoutPaddingTop = a.getDimensionPixelSize( 191 R.styleable.PagedView_pageLayoutPaddingTop, 10); 192 mPageLayoutPaddingBottom = a.getDimensionPixelSize( 193 R.styleable.PagedView_pageLayoutPaddingBottom, 10); 194 mPageLayoutPaddingLeft = a.getDimensionPixelSize( 195 R.styleable.PagedView_pageLayoutPaddingLeft, 10); 196 mPageLayoutPaddingRight = a.getDimensionPixelSize( 197 R.styleable.PagedView_pageLayoutPaddingRight, 10); 198 mPageLayoutWidthGap = a.getDimensionPixelSize( 199 R.styleable.PagedView_pageLayoutWidthGap, -1); 200 mPageLayoutHeightGap = a.getDimensionPixelSize( 201 R.styleable.PagedView_pageLayoutHeightGap, -1); 202 a.recycle(); 203 204 setHapticFeedbackEnabled(false); 205 init(); 206 } 207 208 /** 209 * Initializes various states for this workspace. 210 */ 211 protected void init() { 212 mDirtyPageContent = new ArrayList<Boolean>(); 213 mDirtyPageContent.ensureCapacity(32); 214 mPageViewIconCache = new PagedViewIconCache(); 215 mScroller = new Scroller(getContext(), new ScrollInterpolator()); 216 mCurrentPage = 0; 217 mCenterPagesVertically = true; 218 219 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 220 mTouchSlop = configuration.getScaledTouchSlop(); 221 mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); 222 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 223 } 224 225 public void setPageSwitchListener(PageSwitchListener pageSwitchListener) { 226 mPageSwitchListener = pageSwitchListener; 227 if (mPageSwitchListener != null) { 228 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); 229 } 230 } 231 232 /** 233 * Returns the index of the currently displayed page. 234 * 235 * @return The index of the currently displayed page. 236 */ 237 int getCurrentPage() { 238 return mCurrentPage; 239 } 240 241 int getPageCount() { 242 return getChildCount(); 243 } 244 245 View getPageAt(int index) { 246 return getChildAt(index); 247 } 248 249 int getScrollWidth() { 250 return getWidth(); 251 } 252 253 /** 254 * Updates the scroll of the current page immediately to its final scroll position. We use this 255 * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of 256 * the previous tab page. 257 */ 258 protected void updateCurrentPageScroll() { 259 int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage); 260 scrollTo(newX, 0); 261 mScroller.setFinalX(newX); 262 } 263 264 /** 265 * Sets the current page. 266 */ 267 void setCurrentPage(int currentPage) { 268 if (!mScroller.isFinished()) { 269 mScroller.abortAnimation(); 270 } 271 if (getChildCount() == 0 || currentPage == mCurrentPage) { 272 return; 273 } 274 275 mCurrentPage = Math.max(0, Math.min(currentPage, getPageCount() - 1)); 276 updateCurrentPageScroll(); 277 278 invalidate(); 279 notifyPageSwitchListener(); 280 } 281 282 protected void notifyPageSwitchListener() { 283 if (mPageSwitchListener != null) { 284 mPageSwitchListener.onPageSwitch(getPageAt(mCurrentPage), mCurrentPage); 285 } 286 } 287 288 private void pageBeginMoving() { 289 mIsPageMoving = true; 290 onPageBeginMoving(); 291 } 292 293 private void pageEndMoving() { 294 onPageEndMoving(); 295 mIsPageMoving = false; 296 } 297 298 // a method that subclasses can override to add behavior 299 protected void onPageBeginMoving() { 300 } 301 302 // a method that subclasses can override to add behavior 303 protected void onPageEndMoving() { 304 } 305 306 /** 307 * Registers the specified listener on each page contained in this workspace. 308 * 309 * @param l The listener used to respond to long clicks. 310 */ 311 @Override 312 public void setOnLongClickListener(OnLongClickListener l) { 313 mLongClickListener = l; 314 final int count = getPageCount(); 315 for (int i = 0; i < count; i++) { 316 getPageAt(i).setOnLongClickListener(l); 317 } 318 } 319 320 @Override 321 public void scrollBy(int x, int y) { 322 scrollTo(mUnboundedScrollX + x, mScrollY + y); 323 } 324 325 @Override 326 public void scrollTo(int x, int y) { 327 mUnboundedScrollX = x; 328 329 if (x < 0) { 330 super.scrollTo(0, y); 331 if (mAllowOverScroll) { 332 overScroll(x); 333 } 334 } else if (x > mMaxScrollX) { 335 super.scrollTo(mMaxScrollX, y); 336 if (mAllowOverScroll) { 337 overScroll(x - mMaxScrollX); 338 } 339 } else { 340 super.scrollTo(x, y); 341 } 342 343 mTouchX = x; 344 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 345 } 346 347 // we moved this functionality to a helper function so SmoothPagedView can reuse it 348 protected boolean computeScrollHelper() { 349 if (mScroller.computeScrollOffset()) { 350 mDirtyPageAlpha = true; 351 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 352 invalidate(); 353 return true; 354 } else if (mNextPage != INVALID_PAGE) { 355 mDirtyPageAlpha = true; 356 mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1)); 357 mNextPage = INVALID_PAGE; 358 notifyPageSwitchListener(); 359 // We don't want to trigger a page end moving unless the page has settled 360 // and the user has stopped scrolling 361 if (mTouchState == TOUCH_STATE_REST) { 362 pageEndMoving(); 363 } 364 return true; 365 } 366 return false; 367 } 368 369 @Override 370 public void computeScroll() { 371 computeScrollHelper(); 372 } 373 374 @Override 375 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 376 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 377 final int widthSize = MeasureSpec.getSize(widthMeasureSpec); 378 if (widthMode != MeasureSpec.EXACTLY) { 379 throw new IllegalStateException("Workspace can only be used in EXACTLY mode."); 380 } 381 382 /* Allow the height to be set as WRAP_CONTENT. This allows the particular case 383 * of the All apps view on XLarge displays to not take up more space then it needs. Width 384 * is still not allowed to be set as WRAP_CONTENT since many parts of the code expect 385 * each page to have the same width. 386 */ 387 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 388 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 389 int maxChildHeight = 0; 390 391 final int verticalPadding = mPaddingTop + mPaddingBottom; 392 393 // The children are given the same width and height as the workspace 394 // unless they were set to WRAP_CONTENT 395 final int childCount = getChildCount(); 396 for (int i = 0; i < childCount; i++) { 397 // disallowing padding in paged view (just pass 0) 398 final View child = getChildAt(i); 399 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 400 401 int childWidthMode; 402 if (lp.width == LayoutParams.WRAP_CONTENT) { 403 childWidthMode = MeasureSpec.AT_MOST; 404 } else { 405 childWidthMode = MeasureSpec.EXACTLY; 406 } 407 408 int childHeightMode; 409 if (lp.height == LayoutParams.WRAP_CONTENT) { 410 childHeightMode = MeasureSpec.AT_MOST; 411 } else { 412 childHeightMode = MeasureSpec.EXACTLY; 413 } 414 415 final int childWidthMeasureSpec = 416 MeasureSpec.makeMeasureSpec(widthSize, childWidthMode); 417 final int childHeightMeasureSpec = 418 MeasureSpec.makeMeasureSpec(heightSize - verticalPadding, childHeightMode); 419 420 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 421 maxChildHeight = Math.max(maxChildHeight, child.getMeasuredHeight()); 422 } 423 424 if (heightMode == MeasureSpec.AT_MOST) { 425 heightSize = maxChildHeight + verticalPadding; 426 } 427 if (childCount > 0) { 428 mMaxScrollX = getChildOffset(childCount - 1) - getRelativeChildOffset(childCount - 1); 429 } else { 430 mMaxScrollX = 0; 431 } 432 433 setMeasuredDimension(widthSize, heightSize); 434 } 435 436 @Override 437 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 438 if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) { 439 setHorizontalScrollBarEnabled(false); 440 int newX = getChildOffset(mCurrentPage) - getRelativeChildOffset(mCurrentPage); 441 scrollTo(newX, 0); 442 mScroller.setFinalX(newX); 443 setHorizontalScrollBarEnabled(true); 444 mFirstLayout = false; 445 } 446 447 final int verticalPadding = mPaddingTop + mPaddingBottom; 448 final int childCount = getChildCount(); 449 int childLeft = 0; 450 if (childCount > 0) { 451 childLeft = getRelativeChildOffset(0); 452 } 453 454 for (int i = 0; i < childCount; i++) { 455 final View child = getChildAt(i); 456 if (child.getVisibility() != View.GONE) { 457 final int childWidth = child.getMeasuredWidth(); 458 final int childHeight = child.getMeasuredHeight(); 459 int childTop = mPaddingTop; 460 if (mCenterPagesVertically) { 461 childTop += ((getMeasuredHeight() - verticalPadding) - childHeight) / 2; 462 } 463 child.layout(childLeft, childTop, 464 childLeft + childWidth, childTop + childHeight); 465 childLeft += childWidth + mPageSpacing; 466 } 467 } 468 } 469 470 protected void updateAdjacentPagesAlpha() { 471 if (mFadeInAdjacentScreens) { 472 if (mDirtyPageAlpha || (mTouchState == TOUCH_STATE_SCROLLING) || !mScroller.isFinished()) { 473 int halfScreenSize = getMeasuredWidth() / 2; 474 int screenCenter = mScrollX + halfScreenSize; 475 final int childCount = getChildCount(); 476 for (int i = 0; i < childCount; ++i) { 477 View layout = (View) getChildAt(i); 478 int childWidth = layout.getMeasuredWidth(); 479 int halfChildWidth = (childWidth / 2); 480 int childCenter = getChildOffset(i) + halfChildWidth; 481 482 // On the first layout, we may not have a width nor a proper offset, so for now 483 // we should just assume full page width (and calculate the offset according to 484 // that). 485 if (childWidth <= 0) { 486 childWidth = getMeasuredWidth(); 487 childCenter = (i * childWidth) + (childWidth / 2); 488 } 489 490 int d = halfChildWidth; 491 int distanceFromScreenCenter = childCenter - screenCenter; 492 if (distanceFromScreenCenter > 0) { 493 if (i > 0) { 494 d += getChildAt(i - 1).getMeasuredWidth() / 2; 495 } 496 } else { 497 if (i < childCount - 1) { 498 d += getChildAt(i + 1).getMeasuredWidth() / 2; 499 } 500 } 501 d += mPageSpacing; 502 503 // Preventing potential divide-by-zero 504 d = Math.max(1, d); 505 506 float dimAlpha = (float) (Math.abs(distanceFromScreenCenter)) / d; 507 dimAlpha = Math.max(0.0f, Math.min(1.0f, (dimAlpha * dimAlpha))); 508 float alpha = 1.0f - dimAlpha; 509 510 if (alpha < ALPHA_QUANTIZE_LEVEL) { 511 alpha = 0.0f; 512 } else if (alpha > 1.0f - ALPHA_QUANTIZE_LEVEL) { 513 alpha = 1.0f; 514 } 515 516 if (Float.compare(alpha, layout.getAlpha()) != 0) { 517 layout.setAlpha(alpha); 518 } 519 } 520 mDirtyPageAlpha = false; 521 } 522 } 523 } 524 525 protected void screenScrolled(int screenCenter) { 526 } 527 528 @Override 529 protected void dispatchDraw(Canvas canvas) { 530 int halfScreenSize = getMeasuredWidth() / 2; 531 int screenCenter = mScrollX + halfScreenSize; 532 533 if (screenCenter != mLastScreenCenter) { 534 screenScrolled(screenCenter); 535 updateAdjacentPagesAlpha(); 536 mLastScreenCenter = screenCenter; 537 } 538 539 // Find out which screens are visible; as an optimization we only call draw on them 540 // As an optimization, this code assumes that all pages have the same width as the 0th 541 // page. 542 final int pageCount = getChildCount(); 543 if (pageCount > 0) { 544 final int pageWidth = getChildAt(0).getMeasuredWidth(); 545 final int screenWidth = getMeasuredWidth(); 546 int x = getRelativeChildOffset(0) + pageWidth; 547 int leftScreen = 0; 548 int rightScreen = 0; 549 while (x <= mScrollX) { 550 leftScreen++; 551 x += pageWidth + mPageSpacing; 552 // replace above line with this if you don't assume all pages have same width as 0th 553 // page: 554 // x += getChildAt(leftScreen).getMeasuredWidth(); 555 } 556 rightScreen = leftScreen; 557 while (x < mScrollX + screenWidth) { 558 rightScreen++; 559 x += pageWidth + mPageSpacing; 560 // replace above line with this if you don't assume all pages have same width as 0th 561 // page: 562 //if (rightScreen < pageCount) { 563 // x += getChildAt(rightScreen).getMeasuredWidth(); 564 //} 565 } 566 rightScreen = Math.min(getChildCount() - 1, rightScreen); 567 568 final long drawingTime = getDrawingTime(); 569 for (int i = leftScreen; i <= rightScreen; i++) { 570 drawChild(canvas, getChildAt(i), drawingTime); 571 } 572 } 573 } 574 575 @Override 576 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 577 int page = indexOfChild(child); 578 if (page != mCurrentPage || !mScroller.isFinished()) { 579 snapToPage(page); 580 return true; 581 } 582 return false; 583 } 584 585 @Override 586 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 587 int focusablePage; 588 if (mNextPage != INVALID_PAGE) { 589 focusablePage = mNextPage; 590 } else { 591 focusablePage = mCurrentPage; 592 } 593 View v = getPageAt(focusablePage); 594 if (v != null) { 595 v.requestFocus(direction, previouslyFocusedRect); 596 } 597 return false; 598 } 599 600 @Override 601 public boolean dispatchUnhandledMove(View focused, int direction) { 602 if (direction == View.FOCUS_LEFT) { 603 if (getCurrentPage() > 0) { 604 snapToPage(getCurrentPage() - 1); 605 return true; 606 } 607 } else if (direction == View.FOCUS_RIGHT) { 608 if (getCurrentPage() < getPageCount() - 1) { 609 snapToPage(getCurrentPage() + 1); 610 return true; 611 } 612 } 613 return super.dispatchUnhandledMove(focused, direction); 614 } 615 616 @Override 617 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 618 if (mCurrentPage >= 0 && mCurrentPage < getPageCount()) { 619 getPageAt(mCurrentPage).addFocusables(views, direction); 620 } 621 if (direction == View.FOCUS_LEFT) { 622 if (mCurrentPage > 0) { 623 getPageAt(mCurrentPage - 1).addFocusables(views, direction); 624 } 625 } else if (direction == View.FOCUS_RIGHT){ 626 if (mCurrentPage < getPageCount() - 1) { 627 getPageAt(mCurrentPage + 1).addFocusables(views, direction); 628 } 629 } 630 } 631 632 /** 633 * If one of our descendant views decides that it could be focused now, only 634 * pass that along if it's on the current page. 635 * 636 * This happens when live folders requery, and if they're off page, they 637 * end up calling requestFocus, which pulls it on page. 638 */ 639 @Override 640 public void focusableViewAvailable(View focused) { 641 View current = getPageAt(mCurrentPage); 642 View v = focused; 643 while (true) { 644 if (v == current) { 645 super.focusableViewAvailable(focused); 646 return; 647 } 648 if (v == this) { 649 return; 650 } 651 ViewParent parent = v.getParent(); 652 if (parent instanceof View) { 653 v = (View)v.getParent(); 654 } else { 655 return; 656 } 657 } 658 } 659 660 /** 661 * {@inheritDoc} 662 */ 663 @Override 664 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 665 if (disallowIntercept) { 666 // We need to make sure to cancel our long press if 667 // a scrollable widget takes over touch events 668 final View currentPage = getChildAt(mCurrentPage); 669 currentPage.cancelLongPress(); 670 } 671 super.requestDisallowInterceptTouchEvent(disallowIntercept); 672 } 673 674 @Override 675 public boolean onInterceptTouchEvent(MotionEvent ev) { 676 /* 677 * This method JUST determines whether we want to intercept the motion. 678 * If we return true, onTouchEvent will be called and we do the actual 679 * scrolling there. 680 */ 681 682 // Skip touch handling if there are no pages to swipe 683 if (getChildCount() <= 0) return super.onInterceptTouchEvent(ev); 684 685 /* 686 * Shortcut the most recurring case: the user is in the dragging 687 * state and he is moving his finger. We want to intercept this 688 * motion. 689 */ 690 final int action = ev.getAction(); 691 if ((action == MotionEvent.ACTION_MOVE) && 692 (mTouchState == TOUCH_STATE_SCROLLING)) { 693 return true; 694 } 695 696 switch (action & MotionEvent.ACTION_MASK) { 697 case MotionEvent.ACTION_MOVE: { 698 /* 699 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 700 * whether the user has moved far enough from his original down touch. 701 */ 702 if (mActivePointerId != INVALID_POINTER) { 703 determineScrollingStart(ev); 704 break; 705 } 706 // if mActivePointerId is INVALID_POINTER, then we must have missed an ACTION_DOWN 707 // event. in that case, treat the first occurence of a move event as a ACTION_DOWN 708 // i.e. fall through to the next case (don't break) 709 // (We sometimes miss ACTION_DOWN events in Workspace because it ignores all events 710 // while it's small- this was causing a crash before we checked for INVALID_POINTER) 711 } 712 713 case MotionEvent.ACTION_DOWN: { 714 final float x = ev.getX(); 715 final float y = ev.getY(); 716 // Remember location of down touch 717 mDownMotionX = x; 718 mLastMotionX = x; 719 mLastMotionY = y; 720 mActivePointerId = ev.getPointerId(0); 721 mAllowLongPress = true; 722 723 /* 724 * If being flinged and user touches the screen, initiate drag; 725 * otherwise don't. mScroller.isFinished should be false when 726 * being flinged. 727 */ 728 final int xDist = Math.abs(mScroller.getFinalX() - mScroller.getCurrX()); 729 final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop); 730 if (finishedScrolling) { 731 mTouchState = TOUCH_STATE_REST; 732 mScroller.abortAnimation(); 733 } else { 734 mTouchState = TOUCH_STATE_SCROLLING; 735 } 736 737 // check if this can be the beginning of a tap on the side of the pages 738 // to scroll the current page 739 if ((mTouchState != TOUCH_STATE_PREV_PAGE) && !handlePagingClicks() && 740 (mTouchState != TOUCH_STATE_NEXT_PAGE)) { 741 if (getChildCount() > 0) { 742 int width = getMeasuredWidth(); 743 int offset = getRelativeChildOffset(mCurrentPage); 744 if (x < offset - mPageSpacing) { 745 mTouchState = TOUCH_STATE_PREV_PAGE; 746 } else if (x > (width - offset + mPageSpacing)) { 747 mTouchState = TOUCH_STATE_NEXT_PAGE; 748 } 749 } 750 } 751 break; 752 } 753 754 case MotionEvent.ACTION_CANCEL: 755 case MotionEvent.ACTION_UP: 756 mTouchState = TOUCH_STATE_REST; 757 mAllowLongPress = false; 758 mActivePointerId = INVALID_POINTER; 759 break; 760 761 case MotionEvent.ACTION_POINTER_UP: 762 onSecondaryPointerUp(ev); 763 break; 764 } 765 766 /* 767 * The only time we want to intercept motion events is if we are in the 768 * drag mode. 769 */ 770 return mTouchState != TOUCH_STATE_REST; 771 } 772 773 protected void animateClickFeedback(View v, final Runnable r) { 774 // animate the view slightly to show click feedback running some logic after it is "pressed" 775 Animation anim = AnimationUtils.loadAnimation(getContext(), 776 R.anim.paged_view_click_feedback); 777 anim.setAnimationListener(new AnimationListener() { 778 @Override 779 public void onAnimationStart(Animation animation) {} 780 @Override 781 public void onAnimationRepeat(Animation animation) { 782 r.run(); 783 } 784 @Override 785 public void onAnimationEnd(Animation animation) {} 786 }); 787 v.startAnimation(anim); 788 } 789 790 /* 791 * Determines if we should change the touch state to start scrolling after the 792 * user moves their touch point too far. 793 */ 794 protected void determineScrollingStart(MotionEvent ev) { 795 /* 796 * Locally do absolute value. mLastMotionX is set to the y value 797 * of the down event. 798 */ 799 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 800 final float x = ev.getX(pointerIndex); 801 final float y = ev.getY(pointerIndex); 802 final int xDiff = (int) Math.abs(x - mLastMotionX); 803 final int yDiff = (int) Math.abs(y - mLastMotionY); 804 805 final int touchSlop = mTouchSlop; 806 boolean xPaged = xDiff > mPagingTouchSlop; 807 boolean xMoved = xDiff > touchSlop; 808 boolean yMoved = yDiff > touchSlop; 809 810 if (xMoved || yMoved) { 811 if (mUsePagingTouchSlop ? xPaged : xMoved) { 812 // Scroll if the user moved far enough along the X axis 813 mTouchState = TOUCH_STATE_SCROLLING; 814 mLastMotionX = x; 815 mTouchX = mScrollX; 816 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 817 pageBeginMoving(); 818 } 819 // Either way, cancel any pending longpress 820 if (mAllowLongPress) { 821 mAllowLongPress = false; 822 // Try canceling the long press. It could also have been scheduled 823 // by a distant descendant, so use the mAllowLongPress flag to block 824 // everything 825 final View currentPage = getPageAt(mCurrentPage); 826 if (currentPage != null) { 827 currentPage.cancelLongPress(); 828 } 829 } 830 } 831 } 832 833 protected boolean handlePagingClicks() { 834 return false; 835 } 836 837 // This curve determines how the effect of scrolling over the limits of the page dimishes 838 // as the user pulls further and further from the bounds 839 private float overScrollInfluenceCurve(float f) { 840 f -= 1.0f; 841 return f * f * f + 1.0f; 842 } 843 844 protected void overScroll(float amount) { 845 int screenSize = getMeasuredWidth(); 846 847 float f = (amount / screenSize); 848 849 if (f == 0) return; 850 f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f))); 851 852 int overScrollAmount = (int) Math.round(OVERSCROLL_DAMP_FACTOR * f * screenSize); 853 if (amount < 0) { 854 mScrollX = overScrollAmount; 855 } else { 856 mScrollX = mMaxScrollX + overScrollAmount; 857 } 858 invalidate(); 859 } 860 861 @Override 862 public boolean onTouchEvent(MotionEvent ev) { 863 // Skip touch handling if there are no pages to swipe 864 if (getChildCount() <= 0) return super.onTouchEvent(ev); 865 866 acquireVelocityTrackerAndAddMovement(ev); 867 868 final int action = ev.getAction(); 869 870 switch (action & MotionEvent.ACTION_MASK) { 871 case MotionEvent.ACTION_DOWN: 872 /* 873 * If being flinged and user touches, stop the fling. isFinished 874 * will be false if being flinged. 875 */ 876 if (!mScroller.isFinished()) { 877 mScroller.abortAnimation(); 878 } 879 880 // Remember where the motion event started 881 mDownMotionX = mLastMotionX = ev.getX(); 882 mActivePointerId = ev.getPointerId(0); 883 if (mTouchState == TOUCH_STATE_SCROLLING) { 884 pageBeginMoving(); 885 } 886 break; 887 888 case MotionEvent.ACTION_MOVE: 889 if (mTouchState == TOUCH_STATE_SCROLLING) { 890 // Scroll to follow the motion event 891 final int pointerIndex = ev.findPointerIndex(mActivePointerId); 892 final float x = ev.getX(pointerIndex); 893 final int deltaX = (int) (mLastMotionX - x); 894 mLastMotionX = x; 895 896 if (deltaX != 0) { 897 mTouchX += deltaX; 898 mSmoothingTime = System.nanoTime() / NANOTIME_DIV; 899 if (!mDeferScrollUpdate) { 900 scrollBy(deltaX, 0); 901 } else { 902 invalidate(); 903 } 904 } else { 905 awakenScrollBars(); 906 } 907 } else { 908 determineScrollingStart(ev); 909 } 910 break; 911 912 case MotionEvent.ACTION_UP: 913 if (mTouchState == TOUCH_STATE_SCROLLING) { 914 final int activePointerId = mActivePointerId; 915 final int pointerIndex = ev.findPointerIndex(activePointerId); 916 final float x = ev.getX(pointerIndex); 917 final VelocityTracker velocityTracker = mVelocityTracker; 918 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 919 int velocityX = (int) velocityTracker.getXVelocity(activePointerId); 920 final int deltaX = (int) (x - mDownMotionX); 921 boolean isfling = Math.abs(deltaX) > MIN_LENGTH_FOR_FLING; 922 boolean isSignificantMove = Math.abs(deltaX) > MIN_LENGTH_FOR_MOVE; 923 924 final int snapVelocity = mSnapVelocity; 925 if ((isSignificantMove && deltaX > 0 || 926 (isfling && velocityX > snapVelocity)) && mCurrentPage > 0) { 927 snapToPageWithVelocity(mCurrentPage - 1, velocityX); 928 } else if ((isSignificantMove && deltaX < 0 || 929 (isfling && velocityX < -snapVelocity)) && 930 mCurrentPage < getChildCount() - 1) { 931 snapToPageWithVelocity(mCurrentPage + 1, velocityX); 932 } else { 933 snapToDestination(); 934 } 935 } else if (mTouchState == TOUCH_STATE_PREV_PAGE && !handlePagingClicks()) { 936 // at this point we have not moved beyond the touch slop 937 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 938 // we can just page 939 int nextPage = Math.max(0, mCurrentPage - 1); 940 if (nextPage != mCurrentPage) { 941 snapToPage(nextPage); 942 } else { 943 snapToDestination(); 944 } 945 } else if (mTouchState == TOUCH_STATE_NEXT_PAGE && !handlePagingClicks()) { 946 // at this point we have not moved beyond the touch slop 947 // (otherwise mTouchState would be TOUCH_STATE_SCROLLING), so 948 // we can just page 949 int nextPage = Math.min(getChildCount() - 1, mCurrentPage + 1); 950 if (nextPage != mCurrentPage) { 951 snapToPage(nextPage); 952 } else { 953 snapToDestination(); 954 } 955 } 956 mTouchState = TOUCH_STATE_REST; 957 mActivePointerId = INVALID_POINTER; 958 releaseVelocityTracker(); 959 break; 960 961 case MotionEvent.ACTION_CANCEL: 962 if (mTouchState == TOUCH_STATE_SCROLLING) { 963 snapToDestination(); 964 } 965 mTouchState = TOUCH_STATE_REST; 966 mActivePointerId = INVALID_POINTER; 967 releaseVelocityTracker(); 968 break; 969 970 case MotionEvent.ACTION_POINTER_UP: 971 onSecondaryPointerUp(ev); 972 break; 973 } 974 975 return true; 976 } 977 978 private void acquireVelocityTrackerAndAddMovement(MotionEvent ev) { 979 if (mVelocityTracker == null) { 980 mVelocityTracker = VelocityTracker.obtain(); 981 } 982 mVelocityTracker.addMovement(ev); 983 } 984 985 private void releaseVelocityTracker() { 986 if (mVelocityTracker != null) { 987 mVelocityTracker.recycle(); 988 mVelocityTracker = null; 989 } 990 } 991 992 private void onSecondaryPointerUp(MotionEvent ev) { 993 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> 994 MotionEvent.ACTION_POINTER_INDEX_SHIFT; 995 final int pointerId = ev.getPointerId(pointerIndex); 996 if (pointerId == mActivePointerId) { 997 // This was our active pointer going up. Choose a new 998 // active pointer and adjust accordingly. 999 // TODO: Make this decision more intelligent. 1000 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1001 mLastMotionX = mDownMotionX = ev.getX(newPointerIndex); 1002 mLastMotionY = ev.getY(newPointerIndex); 1003 mActivePointerId = ev.getPointerId(newPointerIndex); 1004 if (mVelocityTracker != null) { 1005 mVelocityTracker.clear(); 1006 } 1007 } 1008 } 1009 1010 @Override 1011 public void requestChildFocus(View child, View focused) { 1012 super.requestChildFocus(child, focused); 1013 int page = indexOfChild(child); 1014 if (page >= 0 && !isInTouchMode()) { 1015 snapToPage(page); 1016 } 1017 } 1018 1019 protected int getChildIndexForRelativeOffset(int relativeOffset) { 1020 final int childCount = getChildCount(); 1021 int left; 1022 int right; 1023 for (int i = 0; i < childCount; ++i) { 1024 left = getRelativeChildOffset(i); 1025 right = (left + getChildAt(i).getMeasuredWidth()); 1026 if (left <= relativeOffset && relativeOffset <= right) { 1027 return i; 1028 } 1029 } 1030 return -1; 1031 } 1032 1033 protected int getRelativeChildOffset(int index) { 1034 return (getMeasuredWidth() - getChildAt(index).getMeasuredWidth()) / 2; 1035 } 1036 1037 protected int getChildOffset(int index) { 1038 if (getChildCount() == 0) 1039 return 0; 1040 1041 int offset = getRelativeChildOffset(0); 1042 for (int i = 0; i < index; ++i) { 1043 offset += getChildAt(i).getMeasuredWidth() + mPageSpacing; 1044 } 1045 return offset; 1046 } 1047 1048 int getPageNearestToCenterOfScreen() { 1049 int minDistanceFromScreenCenter = getMeasuredWidth(); 1050 int minDistanceFromScreenCenterIndex = -1; 1051 int screenCenter = mScrollX + (getMeasuredWidth() / 2); 1052 final int childCount = getChildCount(); 1053 for (int i = 0; i < childCount; ++i) { 1054 View layout = (View) getChildAt(i); 1055 int childWidth = layout.getMeasuredWidth(); 1056 int halfChildWidth = (childWidth / 2); 1057 int childCenter = getChildOffset(i) + halfChildWidth; 1058 int distanceFromScreenCenter = Math.abs(childCenter - screenCenter); 1059 if (distanceFromScreenCenter < minDistanceFromScreenCenter) { 1060 minDistanceFromScreenCenter = distanceFromScreenCenter; 1061 minDistanceFromScreenCenterIndex = i; 1062 } 1063 } 1064 return minDistanceFromScreenCenterIndex; 1065 } 1066 1067 protected void snapToDestination() { 1068 snapToPage(getPageNearestToCenterOfScreen(), PAGE_SNAP_ANIMATION_DURATION); 1069 } 1070 1071 private static class ScrollInterpolator implements Interpolator { 1072 public ScrollInterpolator() { 1073 } 1074 1075 public float getInterpolation(float t) { 1076 t -= 1.0f; 1077 return t*t*t*t*t + 1; 1078 } 1079 } 1080 1081 // We want the duration of the page snap animation to be influenced by the distance that 1082 // the screen has to travel, however, we don't want this duration to be effected in a 1083 // purely linear fashion. Instead, we use this method to moderate the effect that the distance 1084 // of travel has on the overall snap duration. 1085 float distanceInfluenceForSnapDuration(float f) { 1086 f -= 0.5f; // center the values about 0. 1087 f *= 0.3f * Math.PI / 2.0f; 1088 return (float) Math.sin(f); 1089 } 1090 1091 protected void snapToPageWithVelocity(int whichPage, int velocity) { 1092 whichPage = Math.max(0, Math.min(whichPage, getChildCount() - 1)); 1093 int halfScreenSize = getMeasuredWidth() / 2; 1094 1095 final int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); 1096 int delta = newX - mUnboundedScrollX; 1097 int duration = 0; 1098 1099 if (Math.abs(velocity) < MIN_FLING_VELOCITY) { 1100 // If the velocity is low enough, then treat this more as an automatic page advance 1101 // as opposed to an apparent physical response to flinging 1102 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); 1103 return; 1104 } 1105 1106 // Here we compute a "distance" that will be used in the computation of the overall 1107 // snap duration. This is a function of the actual distance that needs to be traveled; 1108 // we keep this value close to half screen size in order to reduce the variance in snap 1109 // duration as a function of the distance the page needs to travel. 1110 float distanceRatio = 1.0f * Math.abs(delta) / 2 * halfScreenSize; 1111 float distance = halfScreenSize + halfScreenSize * 1112 distanceInfluenceForSnapDuration(distanceRatio); 1113 1114 velocity = Math.abs(velocity); 1115 velocity = Math.max(MINIMUM_SNAP_VELOCITY, velocity); 1116 1117 // we want the page's snap velocity to approximately match the velocity at which the 1118 // user flings, so we scale the duration by a value near to the derivative of the scroll 1119 // interpolator at zero, ie. 5. We use 6 to make it a little slower. 1120 duration = 6 * Math.round(1000 * Math.abs(distance / velocity)); 1121 1122 snapToPage(whichPage, delta, duration); 1123 } 1124 1125 protected void snapToPage(int whichPage) { 1126 snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION); 1127 } 1128 1129 protected void snapToPage(int whichPage, int duration) { 1130 whichPage = Math.max(0, Math.min(whichPage, getPageCount() - 1)); 1131 1132 int newX = getChildOffset(whichPage) - getRelativeChildOffset(whichPage); 1133 final int sX = mUnboundedScrollX; 1134 final int delta = newX - sX; 1135 snapToPage(whichPage, delta, duration); 1136 } 1137 1138 protected void snapToPage(int whichPage, int delta, int duration) { 1139 mNextPage = whichPage; 1140 1141 View focusedChild = getFocusedChild(); 1142 if (focusedChild != null && whichPage != mCurrentPage && 1143 focusedChild == getChildAt(mCurrentPage)) { 1144 focusedChild.clearFocus(); 1145 } 1146 1147 pageBeginMoving(); 1148 awakenScrollBars(duration); 1149 if (duration == 0) { 1150 duration = Math.abs(delta); 1151 } 1152 1153 if (!mScroller.isFinished()) mScroller.abortAnimation(); 1154 mScroller.startScroll(mUnboundedScrollX, 0, delta, 0, duration); 1155 1156 // only load some associated pages 1157 loadAssociatedPages(mNextPage); 1158 notifyPageSwitchListener(); 1159 invalidate(); 1160 } 1161 1162 @Override 1163 protected Parcelable onSaveInstanceState() { 1164 final SavedState state = new SavedState(super.onSaveInstanceState()); 1165 state.currentPage = mCurrentPage; 1166 return state; 1167 } 1168 1169 @Override 1170 protected void onRestoreInstanceState(Parcelable state) { 1171 SavedState savedState = (SavedState) state; 1172 super.onRestoreInstanceState(savedState.getSuperState()); 1173 if (savedState.currentPage != -1) { 1174 mCurrentPage = savedState.currentPage; 1175 } 1176 } 1177 1178 public void scrollLeft() { 1179 if (mScroller.isFinished()) { 1180 if (mCurrentPage > 0) snapToPage(mCurrentPage - 1); 1181 } else { 1182 if (mNextPage > 0) snapToPage(mNextPage - 1); 1183 } 1184 } 1185 1186 public void scrollRight() { 1187 if (mScroller.isFinished()) { 1188 if (mCurrentPage < getChildCount() -1) snapToPage(mCurrentPage + 1); 1189 } else { 1190 if (mNextPage < getChildCount() -1) snapToPage(mNextPage + 1); 1191 } 1192 } 1193 1194 public int getPageForView(View v) { 1195 int result = -1; 1196 if (v != null) { 1197 ViewParent vp = v.getParent(); 1198 int count = getChildCount(); 1199 for (int i = 0; i < count; i++) { 1200 if (vp == getChildAt(i)) { 1201 return i; 1202 } 1203 } 1204 } 1205 return result; 1206 } 1207 1208 /** 1209 * @return True is long presses are still allowed for the current touch 1210 */ 1211 public boolean allowLongPress() { 1212 return mAllowLongPress; 1213 } 1214 1215 /** 1216 * Set true to allow long-press events to be triggered, usually checked by 1217 * {@link Launcher} to accept or block dpad-initiated long-presses. 1218 */ 1219 public void setAllowLongPress(boolean allowLongPress) { 1220 mAllowLongPress = allowLongPress; 1221 } 1222 1223 public static class SavedState extends BaseSavedState { 1224 int currentPage = -1; 1225 1226 SavedState(Parcelable superState) { 1227 super(superState); 1228 } 1229 1230 private SavedState(Parcel in) { 1231 super(in); 1232 currentPage = in.readInt(); 1233 } 1234 1235 @Override 1236 public void writeToParcel(Parcel out, int flags) { 1237 super.writeToParcel(out, flags); 1238 out.writeInt(currentPage); 1239 } 1240 1241 public static final Parcelable.Creator<SavedState> CREATOR = 1242 new Parcelable.Creator<SavedState>() { 1243 public SavedState createFromParcel(Parcel in) { 1244 return new SavedState(in); 1245 } 1246 1247 public SavedState[] newArray(int size) { 1248 return new SavedState[size]; 1249 } 1250 }; 1251 } 1252 1253 public void loadAssociatedPages(int page) { 1254 if (mContentIsRefreshable) { 1255 final int count = getChildCount(); 1256 if (page < count) { 1257 int lowerPageBound = getAssociatedLowerPageBound(page); 1258 int upperPageBound = getAssociatedUpperPageBound(page); 1259 for (int i = 0; i < count; ++i) { 1260 final ViewGroup layout = (ViewGroup) getChildAt(i); 1261 final int childCount = layout.getChildCount(); 1262 if (lowerPageBound <= i && i <= upperPageBound) { 1263 if (mDirtyPageContent.get(i)) { 1264 syncPageItems(i); 1265 mDirtyPageContent.set(i, false); 1266 } 1267 } else { 1268 if (childCount > 0) { 1269 layout.removeAllViews(); 1270 } 1271 mDirtyPageContent.set(i, true); 1272 } 1273 } 1274 } 1275 } 1276 } 1277 1278 protected int getAssociatedLowerPageBound(int page) { 1279 return Math.max(0, page - 1); 1280 } 1281 protected int getAssociatedUpperPageBound(int page) { 1282 final int count = getChildCount(); 1283 return Math.min(page + 1, count - 1); 1284 } 1285 1286 protected void startChoiceMode(int mode, ActionMode.Callback callback) { 1287 if (isChoiceMode(CHOICE_MODE_NONE)) { 1288 mChoiceMode = mode; 1289 mActionMode = startActionMode(callback); 1290 } 1291 } 1292 1293 public void endChoiceMode() { 1294 if (!isChoiceMode(CHOICE_MODE_NONE)) { 1295 mChoiceMode = CHOICE_MODE_NONE; 1296 resetCheckedGrandchildren(); 1297 if (mActionMode != null) mActionMode.finish(); 1298 mActionMode = null; 1299 } 1300 } 1301 1302 protected boolean isChoiceMode(int mode) { 1303 return mChoiceMode == mode; 1304 } 1305 1306 protected ArrayList<Checkable> getCheckedGrandchildren() { 1307 ArrayList<Checkable> checked = new ArrayList<Checkable>(); 1308 final int childCount = getChildCount(); 1309 for (int i = 0; i < childCount; ++i) { 1310 final ViewGroup layout = (ViewGroup) getChildAt(i); 1311 final int grandChildCount = layout.getChildCount(); 1312 for (int j = 0; j < grandChildCount; ++j) { 1313 final View v = layout.getChildAt(j); 1314 if (v instanceof Checkable && ((Checkable) v).isChecked()) { 1315 checked.add((Checkable) v); 1316 } 1317 } 1318 } 1319 return checked; 1320 } 1321 1322 /** 1323 * If in CHOICE_MODE_SINGLE and an item is checked, returns that item. 1324 * Otherwise, returns null. 1325 */ 1326 protected Checkable getSingleCheckedGrandchild() { 1327 if (mChoiceMode == CHOICE_MODE_SINGLE) { 1328 final int childCount = getChildCount(); 1329 for (int i = 0; i < childCount; ++i) { 1330 final ViewGroup layout = (ViewGroup) getChildAt(i); 1331 final int grandChildCount = layout.getChildCount(); 1332 for (int j = 0; j < grandChildCount; ++j) { 1333 final View v = layout.getChildAt(j); 1334 if (v instanceof Checkable && ((Checkable) v).isChecked()) { 1335 return (Checkable) v; 1336 } 1337 } 1338 } 1339 } 1340 return null; 1341 } 1342 1343 public Object getChosenItem() { 1344 View checkedView = (View) getSingleCheckedGrandchild(); 1345 if (checkedView != null) { 1346 return checkedView.getTag(); 1347 } 1348 return null; 1349 } 1350 1351 protected void resetCheckedGrandchildren() { 1352 // loop through children, and set all of their children to _not_ be checked 1353 final ArrayList<Checkable> checked = getCheckedGrandchildren(); 1354 for (int i = 0; i < checked.size(); ++i) { 1355 final Checkable c = checked.get(i); 1356 c.setChecked(false); 1357 } 1358 } 1359 1360 /** 1361 * This method is called ONLY to synchronize the number of pages that the paged view has. 1362 * To actually fill the pages with information, implement syncPageItems() below. It is 1363 * guaranteed that syncPageItems() will be called for a particular page before it is shown, 1364 * and therefore, individual page items do not need to be updated in this method. 1365 */ 1366 public abstract void syncPages(); 1367 1368 /** 1369 * This method is called to synchronize the items that are on a particular page. If views on 1370 * the page can be reused, then they should be updated within this method. 1371 */ 1372 public abstract void syncPageItems(int page); 1373 1374 public void invalidatePageData() { 1375 if (mContentIsRefreshable) { 1376 // Update all the pages 1377 syncPages(); 1378 1379 // Mark each of the pages as dirty 1380 final int count = getChildCount(); 1381 mDirtyPageContent.clear(); 1382 for (int i = 0; i < count; ++i) { 1383 mDirtyPageContent.add(true); 1384 } 1385 1386 // Load any pages that are necessary for the current window of views 1387 loadAssociatedPages(mCurrentPage); 1388 mDirtyPageAlpha = true; 1389 updateAdjacentPagesAlpha(); 1390 requestLayout(); 1391 } 1392 } 1393} 1394