ViewPager.java revision 035f6aa81bbb439d2aa20dcd2eac4459a76d561e
1/* 2 * Copyright (C) 2011 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 android.support.v4.view; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.database.DataSetObserver; 22import android.graphics.Canvas; 23import android.graphics.Rect; 24import android.graphics.drawable.Drawable; 25import android.os.Build; 26import android.os.Parcel; 27import android.os.Parcelable; 28import android.os.SystemClock; 29import android.support.v4.os.ParcelableCompat; 30import android.support.v4.os.ParcelableCompatCreatorCallbacks; 31import android.support.v4.widget.EdgeEffectCompat; 32import android.util.AttributeSet; 33import android.util.Log; 34import android.view.FocusFinder; 35import android.view.Gravity; 36import android.view.KeyEvent; 37import android.view.MotionEvent; 38import android.view.SoundEffectConstants; 39import android.view.VelocityTracker; 40import android.view.View; 41import android.view.ViewConfiguration; 42import android.view.ViewGroup; 43import android.view.ViewParent; 44import android.view.accessibility.AccessibilityEvent; 45import android.view.animation.Interpolator; 46import android.widget.Scroller; 47 48import java.util.ArrayList; 49import java.util.Collections; 50import java.util.Comparator; 51 52/** 53 * Layout manager that allows the user to flip left and right 54 * through pages of data. You supply an implementation of a 55 * {@link PagerAdapter} to generate the pages that the view shows. 56 * 57 * <p>Note this class is currently under early design and 58 * development. The API will likely change in later updates of 59 * the compatibility library, requiring changes to the source code 60 * of apps when they are compiled against the newer version.</p> 61 */ 62public class ViewPager extends ViewGroup { 63 private static final String TAG = "ViewPager"; 64 private static final boolean DEBUG = false; 65 66 private static final boolean USE_CACHE = false; 67 68 private static final int DEFAULT_OFFSCREEN_PAGES = 1; 69 private static final int MAX_SETTLE_DURATION = 600; // ms 70 private static final int MIN_DISTANCE_FOR_FLING = 25; // dips 71 72 private static final int[] LAYOUT_ATTRS = new int[] { 73 android.R.attr.layout_gravity 74 }; 75 76 static class ItemInfo { 77 Object object; 78 int position; 79 boolean scrolling; 80 } 81 82 private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){ 83 @Override 84 public int compare(ItemInfo lhs, ItemInfo rhs) { 85 return lhs.position - rhs.position; 86 }}; 87 88 private static final Interpolator sInterpolator = new Interpolator() { 89 public float getInterpolation(float t) { 90 t -= 1.0f; 91 return t * t * t * t * t + 1.0f; 92 } 93 }; 94 95 private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>(); 96 97 private PagerAdapter mAdapter; 98 private int mCurItem; // Index of currently displayed page. 99 private int mRestoredCurItem = -1; 100 private Parcelable mRestoredAdapterState = null; 101 private ClassLoader mRestoredClassLoader = null; 102 private Scroller mScroller; 103 private PagerObserver mObserver; 104 105 private int mPageMargin; 106 private Drawable mMarginDrawable; 107 private int mTopPageBounds; 108 private int mBottomPageBounds; 109 110 private int mChildWidthMeasureSpec; 111 private int mChildHeightMeasureSpec; 112 private boolean mInLayout; 113 114 private boolean mScrollingCacheEnabled; 115 116 private boolean mPopulatePending; 117 private boolean mScrolling; 118 private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES; 119 120 private boolean mIsBeingDragged; 121 private boolean mIsUnableToDrag; 122 private int mTouchSlop; 123 private float mInitialMotionX; 124 /** 125 * Position of the last motion event. 126 */ 127 private float mLastMotionX; 128 private float mLastMotionY; 129 /** 130 * ID of the active pointer. This is used to retain consistency during 131 * drags/flings if multiple pointers are used. 132 */ 133 private int mActivePointerId = INVALID_POINTER; 134 /** 135 * Sentinel value for no current active pointer. 136 * Used by {@link #mActivePointerId}. 137 */ 138 private static final int INVALID_POINTER = -1; 139 140 /** 141 * Determines speed during touch scrolling 142 */ 143 private VelocityTracker mVelocityTracker; 144 private int mMinimumVelocity; 145 private int mMaximumVelocity; 146 private int mFlingDistance; 147 148 private boolean mFakeDragging; 149 private long mFakeDragBeginTime; 150 151 private EdgeEffectCompat mLeftEdge; 152 private EdgeEffectCompat mRightEdge; 153 154 private boolean mFirstLayout = true; 155 private boolean mCalledSuper; 156 private int mDecorChildCount; 157 158 private OnPageChangeListener mOnPageChangeListener; 159 private OnPageChangeListener mInternalPageChangeListener; 160 private OnAdapterChangeListener mAdapterChangeListener; 161 162 /** 163 * Indicates that the pager is in an idle, settled state. The current page 164 * is fully in view and no animation is in progress. 165 */ 166 public static final int SCROLL_STATE_IDLE = 0; 167 168 /** 169 * Indicates that the pager is currently being dragged by the user. 170 */ 171 public static final int SCROLL_STATE_DRAGGING = 1; 172 173 /** 174 * Indicates that the pager is in the process of settling to a final position. 175 */ 176 public static final int SCROLL_STATE_SETTLING = 2; 177 178 private int mScrollState = SCROLL_STATE_IDLE; 179 180 /** 181 * Callback interface for responding to changing state of the selected page. 182 */ 183 public interface OnPageChangeListener { 184 185 /** 186 * This method will be invoked when the current page is scrolled, either as part 187 * of a programmatically initiated smooth scroll or a user initiated touch scroll. 188 * 189 * @param position Position index of the first page currently being displayed. 190 * Page position+1 will be visible if positionOffset is nonzero. 191 * @param positionOffset Value from [0, 1) indicating the offset from the page at position. 192 * @param positionOffsetPixels Value in pixels indicating the offset from position. 193 */ 194 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels); 195 196 /** 197 * This method will be invoked when a new page becomes selected. Animation is not 198 * necessarily complete. 199 * 200 * @param position Position index of the new selected page. 201 */ 202 public void onPageSelected(int position); 203 204 /** 205 * Called when the scroll state changes. Useful for discovering when the user 206 * begins dragging, when the pager is automatically settling to the current page, 207 * or when it is fully stopped/idle. 208 * 209 * @param state The new scroll state. 210 * @see ViewPager#SCROLL_STATE_IDLE 211 * @see ViewPager#SCROLL_STATE_DRAGGING 212 * @see ViewPager#SCROLL_STATE_SETTLING 213 */ 214 public void onPageScrollStateChanged(int state); 215 } 216 217 /** 218 * Simple implementation of the {@link OnPageChangeListener} interface with stub 219 * implementations of each method. Extend this if you do not intend to override 220 * every method of {@link OnPageChangeListener}. 221 */ 222 public static class SimpleOnPageChangeListener implements OnPageChangeListener { 223 @Override 224 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 225 // This space for rent 226 } 227 228 @Override 229 public void onPageSelected(int position) { 230 // This space for rent 231 } 232 233 @Override 234 public void onPageScrollStateChanged(int state) { 235 // This space for rent 236 } 237 } 238 239 /** 240 * Used internally to monitor when adapters are switched. 241 */ 242 interface OnAdapterChangeListener { 243 public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter); 244 } 245 246 /** 247 * Used internally to tag special types of child views that should be added as 248 * pager decorations by default. 249 */ 250 interface Decor {} 251 252 public ViewPager(Context context) { 253 super(context); 254 initViewPager(); 255 } 256 257 public ViewPager(Context context, AttributeSet attrs) { 258 super(context, attrs); 259 initViewPager(); 260 } 261 262 void initViewPager() { 263 setWillNotDraw(false); 264 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); 265 setFocusable(true); 266 final Context context = getContext(); 267 mScroller = new Scroller(context, sInterpolator); 268 final ViewConfiguration configuration = ViewConfiguration.get(context); 269 mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); 270 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); 271 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); 272 mLeftEdge = new EdgeEffectCompat(context); 273 mRightEdge = new EdgeEffectCompat(context); 274 275 final float density = context.getResources().getDisplayMetrics().density; 276 mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density); 277 } 278 279 private void setScrollState(int newState) { 280 if (mScrollState == newState) { 281 return; 282 } 283 284 mScrollState = newState; 285 if (mOnPageChangeListener != null) { 286 mOnPageChangeListener.onPageScrollStateChanged(newState); 287 } 288 } 289 290 /** 291 * Set a PagerAdapter that will supply views for this pager as needed. 292 * 293 * @param adapter Adapter to use 294 */ 295 public void setAdapter(PagerAdapter adapter) { 296 if (mAdapter != null) { 297 mAdapter.unregisterDataSetObserver(mObserver); 298 mAdapter.startUpdate(this); 299 for (int i = 0; i < mItems.size(); i++) { 300 final ItemInfo ii = mItems.get(i); 301 mAdapter.destroyItem(this, ii.position, ii.object); 302 } 303 mAdapter.finishUpdate(this); 304 mItems.clear(); 305 removeNonDecorViews(); 306 mCurItem = 0; 307 scrollTo(0, 0); 308 } 309 310 final PagerAdapter oldAdapter = mAdapter; 311 mAdapter = adapter; 312 313 if (mAdapter != null) { 314 if (mObserver == null) { 315 mObserver = new PagerObserver(); 316 } 317 mAdapter.registerDataSetObserver(mObserver); 318 mPopulatePending = false; 319 if (mRestoredCurItem >= 0) { 320 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader); 321 setCurrentItemInternal(mRestoredCurItem, false, true); 322 mRestoredCurItem = -1; 323 mRestoredAdapterState = null; 324 mRestoredClassLoader = null; 325 } else { 326 populate(); 327 } 328 } 329 330 if (mAdapterChangeListener != null && oldAdapter != adapter) { 331 mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter); 332 } 333 } 334 335 private void removeNonDecorViews() { 336 for (int i = 0; i < getChildCount(); i++) { 337 final View child = getChildAt(i); 338 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 339 if (!lp.isDecor) { 340 removeViewAt(i); 341 i--; 342 } 343 } 344 } 345 346 /** 347 * Retrieve the current adapter supplying pages. 348 * 349 * @return The currently registered PagerAdapter 350 */ 351 public PagerAdapter getAdapter() { 352 return mAdapter; 353 } 354 355 void setOnAdapterChangeListener(OnAdapterChangeListener listener) { 356 mAdapterChangeListener = listener; 357 } 358 359 /** 360 * Set the currently selected page. If the ViewPager has already been through its first 361 * layout there will be a smooth animated transition between the current item and the 362 * specified item. 363 * 364 * @param item Item index to select 365 */ 366 public void setCurrentItem(int item) { 367 mPopulatePending = false; 368 setCurrentItemInternal(item, !mFirstLayout, false); 369 } 370 371 /** 372 * Set the currently selected page. 373 * 374 * @param item Item index to select 375 * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately 376 */ 377 public void setCurrentItem(int item, boolean smoothScroll) { 378 mPopulatePending = false; 379 setCurrentItemInternal(item, smoothScroll, false); 380 } 381 382 public int getCurrentItem() { 383 return mCurItem; 384 } 385 386 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) { 387 setCurrentItemInternal(item, smoothScroll, always, 0); 388 } 389 390 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) { 391 if (mAdapter == null || mAdapter.getCount() <= 0) { 392 setScrollingCacheEnabled(false); 393 return; 394 } 395 if (!always && mCurItem == item && mItems.size() != 0) { 396 setScrollingCacheEnabled(false); 397 return; 398 } 399 if (item < 0) { 400 item = 0; 401 } else if (item >= mAdapter.getCount()) { 402 item = mAdapter.getCount() - 1; 403 } 404 final int pageLimit = mOffscreenPageLimit; 405 if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) { 406 // We are doing a jump by more than one page. To avoid 407 // glitches, we want to keep all current pages in the view 408 // until the scroll ends. 409 for (int i=0; i<mItems.size(); i++) { 410 mItems.get(i).scrolling = true; 411 } 412 } 413 final boolean dispatchSelected = mCurItem != item; 414 mCurItem = item; 415 populate(); 416 final int destX = (getWidth() + mPageMargin) * item; 417 if (smoothScroll) { 418 smoothScrollTo(destX, 0, velocity); 419 if (dispatchSelected && mOnPageChangeListener != null) { 420 mOnPageChangeListener.onPageSelected(item); 421 } 422 if (dispatchSelected && mInternalPageChangeListener != null) { 423 mInternalPageChangeListener.onPageSelected(item); 424 } 425 } else { 426 if (dispatchSelected && mOnPageChangeListener != null) { 427 mOnPageChangeListener.onPageSelected(item); 428 } 429 if (dispatchSelected && mInternalPageChangeListener != null) { 430 mInternalPageChangeListener.onPageSelected(item); 431 } 432 completeScroll(); 433 scrollTo(destX, 0); 434 } 435 } 436 437 /** 438 * Set a listener that will be invoked whenever the page changes or is incrementally 439 * scrolled. See {@link OnPageChangeListener}. 440 * 441 * @param listener Listener to set 442 */ 443 public void setOnPageChangeListener(OnPageChangeListener listener) { 444 mOnPageChangeListener = listener; 445 } 446 447 /** 448 * Set a separate OnPageChangeListener for internal use by the support library. 449 * 450 * @param listener Listener to set 451 * @return The old listener that was set, if any. 452 */ 453 OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) { 454 OnPageChangeListener oldListener = mInternalPageChangeListener; 455 mInternalPageChangeListener = listener; 456 return oldListener; 457 } 458 459 /** 460 * Returns the number of pages that will be retained to either side of the 461 * current page in the view hierarchy in an idle state. Defaults to 1. 462 * 463 * @return How many pages will be kept offscreen on either side 464 * @see #setOffscreenPageLimit(int) 465 */ 466 public int getOffscreenPageLimit() { 467 return mOffscreenPageLimit; 468 } 469 470 /** 471 * Set the number of pages that should be retained to either side of the 472 * current page in the view hierarchy in an idle state. Pages beyond this 473 * limit will be recreated from the adapter when needed. 474 * 475 * <p>This is offered as an optimization. If you know in advance the number 476 * of pages you will need to support or have lazy-loading mechanisms in place 477 * on your pages, tweaking this setting can have benefits in perceived smoothness 478 * of paging animations and interaction. If you have a small number of pages (3-4) 479 * that you can keep active all at once, less time will be spent in layout for 480 * newly created view subtrees as the user pages back and forth.</p> 481 * 482 * <p>You should keep this limit low, especially if your pages have complex layouts. 483 * This setting defaults to 1.</p> 484 * 485 * @param limit How many pages will be kept offscreen in an idle state. 486 */ 487 public void setOffscreenPageLimit(int limit) { 488 if (limit < DEFAULT_OFFSCREEN_PAGES) { 489 Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " + 490 DEFAULT_OFFSCREEN_PAGES); 491 limit = DEFAULT_OFFSCREEN_PAGES; 492 } 493 if (limit != mOffscreenPageLimit) { 494 mOffscreenPageLimit = limit; 495 populate(); 496 } 497 } 498 499 /** 500 * Set the margin between pages. 501 * 502 * @param marginPixels Distance between adjacent pages in pixels 503 * @see #getPageMargin() 504 * @see #setPageMarginDrawable(Drawable) 505 * @see #setPageMarginDrawable(int) 506 */ 507 public void setPageMargin(int marginPixels) { 508 final int oldMargin = mPageMargin; 509 mPageMargin = marginPixels; 510 511 final int width = getWidth(); 512 recomputeScrollPosition(width, width, marginPixels, oldMargin); 513 514 requestLayout(); 515 } 516 517 /** 518 * Return the margin between pages. 519 * 520 * @return The size of the margin in pixels 521 */ 522 public int getPageMargin() { 523 return mPageMargin; 524 } 525 526 /** 527 * Set a drawable that will be used to fill the margin between pages. 528 * 529 * @param d Drawable to display between pages 530 */ 531 public void setPageMarginDrawable(Drawable d) { 532 mMarginDrawable = d; 533 if (d != null) refreshDrawableState(); 534 setWillNotDraw(d == null); 535 invalidate(); 536 } 537 538 /** 539 * Set a drawable that will be used to fill the margin between pages. 540 * 541 * @param resId Resource ID of a drawable to display between pages 542 */ 543 public void setPageMarginDrawable(int resId) { 544 setPageMarginDrawable(getContext().getResources().getDrawable(resId)); 545 } 546 547 @Override 548 protected boolean verifyDrawable(Drawable who) { 549 return super.verifyDrawable(who) || who == mMarginDrawable; 550 } 551 552 @Override 553 protected void drawableStateChanged() { 554 super.drawableStateChanged(); 555 final Drawable d = mMarginDrawable; 556 if (d != null && d.isStateful()) { 557 d.setState(getDrawableState()); 558 } 559 } 560 561 // We want the duration of the page snap animation to be influenced by the distance that 562 // the screen has to travel, however, we don't want this duration to be effected in a 563 // purely linear fashion. Instead, we use this method to moderate the effect that the distance 564 // of travel has on the overall snap duration. 565 float distanceInfluenceForSnapDuration(float f) { 566 f -= 0.5f; // center the values about 0. 567 f *= 0.3f * Math.PI / 2.0f; 568 return (float) Math.sin(f); 569 } 570 571 /** 572 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 573 * 574 * @param x the number of pixels to scroll by on the X axis 575 * @param y the number of pixels to scroll by on the Y axis 576 */ 577 void smoothScrollTo(int x, int y) { 578 smoothScrollTo(x, y, 0); 579 } 580 581 /** 582 * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 583 * 584 * @param x the number of pixels to scroll by on the X axis 585 * @param y the number of pixels to scroll by on the Y axis 586 * @param velocity the velocity associated with a fling, if applicable. (0 otherwise) 587 */ 588 void smoothScrollTo(int x, int y, int velocity) { 589 if (getChildCount() == 0) { 590 // Nothing to do. 591 setScrollingCacheEnabled(false); 592 return; 593 } 594 int sx = getScrollX(); 595 int sy = getScrollY(); 596 int dx = x - sx; 597 int dy = y - sy; 598 if (dx == 0 && dy == 0) { 599 completeScroll(); 600 setScrollState(SCROLL_STATE_IDLE); 601 return; 602 } 603 604 setScrollingCacheEnabled(true); 605 mScrolling = true; 606 setScrollState(SCROLL_STATE_SETTLING); 607 608 final int width = getWidth(); 609 final int halfWidth = width / 2; 610 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width); 611 final float distance = halfWidth + halfWidth * 612 distanceInfluenceForSnapDuration(distanceRatio); 613 614 int duration = 0; 615 velocity = Math.abs(velocity); 616 if (velocity > 0) { 617 duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); 618 } else { 619 final float pageDelta = (float) Math.abs(dx) / (width + mPageMargin); 620 duration = (int) ((pageDelta + 1) * 100); 621 } 622 duration = Math.min(duration, MAX_SETTLE_DURATION); 623 624 mScroller.startScroll(sx, sy, dx, dy, duration); 625 invalidate(); 626 } 627 628 void addNewItem(int position, int index) { 629 ItemInfo ii = new ItemInfo(); 630 ii.position = position; 631 ii.object = mAdapter.instantiateItem(this, position); 632 if (index < 0) { 633 mItems.add(ii); 634 } else { 635 mItems.add(index, ii); 636 } 637 } 638 639 void dataSetChanged() { 640 // This method only gets called if our observer is attached, so mAdapter is non-null. 641 642 boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount(); 643 int newCurrItem = -1; 644 645 boolean isUpdating = false; 646 for (int i = 0; i < mItems.size(); i++) { 647 final ItemInfo ii = mItems.get(i); 648 final int newPos = mAdapter.getItemPosition(ii.object); 649 650 if (newPos == PagerAdapter.POSITION_UNCHANGED) { 651 continue; 652 } 653 654 if (newPos == PagerAdapter.POSITION_NONE) { 655 mItems.remove(i); 656 i--; 657 658 if (!isUpdating) { 659 mAdapter.startUpdate(this); 660 isUpdating = true; 661 } 662 663 mAdapter.destroyItem(this, ii.position, ii.object); 664 needPopulate = true; 665 666 if (mCurItem == ii.position) { 667 // Keep the current item in the valid range 668 newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1)); 669 } 670 continue; 671 } 672 673 if (ii.position != newPos) { 674 if (ii.position == mCurItem) { 675 // Our current item changed position. Follow it. 676 newCurrItem = newPos; 677 } 678 679 ii.position = newPos; 680 needPopulate = true; 681 } 682 } 683 684 if (isUpdating) { 685 mAdapter.finishUpdate(this); 686 } 687 688 Collections.sort(mItems, COMPARATOR); 689 690 if (newCurrItem >= 0) { 691 // TODO This currently causes a jump. 692 setCurrentItemInternal(newCurrItem, false, true); 693 needPopulate = true; 694 } 695 if (needPopulate) { 696 populate(); 697 requestLayout(); 698 } 699 } 700 701 void populate() { 702 if (mAdapter == null) { 703 return; 704 } 705 706 // Bail now if we are waiting to populate. This is to hold off 707 // on creating views from the time the user releases their finger to 708 // fling to a new position until we have finished the scroll to 709 // that position, avoiding glitches from happening at that point. 710 if (mPopulatePending) { 711 if (DEBUG) Log.i(TAG, "populate is pending, skipping for now..."); 712 return; 713 } 714 715 // Also, don't populate until we are attached to a window. This is to 716 // avoid trying to populate before we have restored our view hierarchy 717 // state and conflicting with what is restored. 718 if (getWindowToken() == null) { 719 return; 720 } 721 722 mAdapter.startUpdate(this); 723 724 final int pageLimit = mOffscreenPageLimit; 725 final int startPos = Math.max(0, mCurItem - pageLimit); 726 final int N = mAdapter.getCount(); 727 final int endPos = Math.min(N-1, mCurItem + pageLimit); 728 729 if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos); 730 731 // Add and remove pages in the existing list. 732 int lastPos = -1; 733 for (int i=0; i<mItems.size(); i++) { 734 ItemInfo ii = mItems.get(i); 735 if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) { 736 if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i); 737 mItems.remove(i); 738 i--; 739 mAdapter.destroyItem(this, ii.position, ii.object); 740 } else if (lastPos < endPos && ii.position > startPos) { 741 // The next item is outside of our range, but we have a gap 742 // between it and the last item where we want to have a page 743 // shown. Fill in the gap. 744 lastPos++; 745 if (lastPos < startPos) { 746 lastPos = startPos; 747 } 748 while (lastPos <= endPos && lastPos < ii.position) { 749 if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i); 750 addNewItem(lastPos, i); 751 lastPos++; 752 i++; 753 } 754 } 755 lastPos = ii.position; 756 } 757 758 // Add any new pages we need at the end. 759 lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1; 760 if (lastPos < endPos) { 761 lastPos++; 762 lastPos = lastPos > startPos ? lastPos : startPos; 763 while (lastPos <= endPos) { 764 if (DEBUG) Log.i(TAG, "appending: " + lastPos); 765 addNewItem(lastPos, -1); 766 lastPos++; 767 } 768 } 769 770 if (DEBUG) { 771 Log.i(TAG, "Current page list:"); 772 for (int i=0; i<mItems.size(); i++) { 773 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position); 774 } 775 } 776 777 ItemInfo curItem = null; 778 for (int i=0; i<mItems.size(); i++) { 779 if (mItems.get(i).position == mCurItem) { 780 curItem = mItems.get(i); 781 break; 782 } 783 } 784 mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null); 785 786 mAdapter.finishUpdate(this); 787 788 if (hasFocus()) { 789 View currentFocused = findFocus(); 790 ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null; 791 if (ii == null || ii.position != mCurItem) { 792 for (int i=0; i<getChildCount(); i++) { 793 View child = getChildAt(i); 794 ii = infoForChild(child); 795 if (ii != null && ii.position == mCurItem) { 796 if (child.requestFocus(FOCUS_FORWARD)) { 797 break; 798 } 799 } 800 } 801 } 802 } 803 } 804 805 public static class SavedState extends BaseSavedState { 806 int position; 807 Parcelable adapterState; 808 ClassLoader loader; 809 810 public SavedState(Parcelable superState) { 811 super(superState); 812 } 813 814 @Override 815 public void writeToParcel(Parcel out, int flags) { 816 super.writeToParcel(out, flags); 817 out.writeInt(position); 818 out.writeParcelable(adapterState, flags); 819 } 820 821 @Override 822 public String toString() { 823 return "FragmentPager.SavedState{" 824 + Integer.toHexString(System.identityHashCode(this)) 825 + " position=" + position + "}"; 826 } 827 828 public static final Parcelable.Creator<SavedState> CREATOR 829 = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() { 830 @Override 831 public SavedState createFromParcel(Parcel in, ClassLoader loader) { 832 return new SavedState(in, loader); 833 } 834 @Override 835 public SavedState[] newArray(int size) { 836 return new SavedState[size]; 837 } 838 }); 839 840 SavedState(Parcel in, ClassLoader loader) { 841 super(in); 842 if (loader == null) { 843 loader = getClass().getClassLoader(); 844 } 845 position = in.readInt(); 846 adapterState = in.readParcelable(loader); 847 this.loader = loader; 848 } 849 } 850 851 @Override 852 public Parcelable onSaveInstanceState() { 853 Parcelable superState = super.onSaveInstanceState(); 854 SavedState ss = new SavedState(superState); 855 ss.position = mCurItem; 856 if (mAdapter != null) { 857 ss.adapterState = mAdapter.saveState(); 858 } 859 return ss; 860 } 861 862 @Override 863 public void onRestoreInstanceState(Parcelable state) { 864 if (!(state instanceof SavedState)) { 865 super.onRestoreInstanceState(state); 866 return; 867 } 868 869 SavedState ss = (SavedState)state; 870 super.onRestoreInstanceState(ss.getSuperState()); 871 872 if (mAdapter != null) { 873 mAdapter.restoreState(ss.adapterState, ss.loader); 874 setCurrentItemInternal(ss.position, false, true); 875 } else { 876 mRestoredCurItem = ss.position; 877 mRestoredAdapterState = ss.adapterState; 878 mRestoredClassLoader = ss.loader; 879 } 880 } 881 882 @Override 883 public void addView(View child, int index, ViewGroup.LayoutParams params) { 884 if (!checkLayoutParams(params)) { 885 params = generateLayoutParams(params); 886 } 887 final LayoutParams lp = (LayoutParams) params; 888 lp.isDecor |= child instanceof Decor; 889 if (mInLayout) { 890 if (lp != null && lp.isDecor) { 891 throw new IllegalStateException("Cannot add pager decor view during layout"); 892 } 893 addViewInLayout(child, index, params); 894 child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec); 895 } else { 896 super.addView(child, index, params); 897 } 898 899 if (USE_CACHE) { 900 if (child.getVisibility() != GONE) { 901 child.setDrawingCacheEnabled(mScrollingCacheEnabled); 902 } else { 903 child.setDrawingCacheEnabled(false); 904 } 905 } 906 } 907 908 ItemInfo infoForChild(View child) { 909 for (int i=0; i<mItems.size(); i++) { 910 ItemInfo ii = mItems.get(i); 911 if (mAdapter.isViewFromObject(child, ii.object)) { 912 return ii; 913 } 914 } 915 return null; 916 } 917 918 ItemInfo infoForAnyChild(View child) { 919 ViewParent parent; 920 while ((parent=child.getParent()) != this) { 921 if (parent == null || !(parent instanceof View)) { 922 return null; 923 } 924 child = (View)parent; 925 } 926 return infoForChild(child); 927 } 928 929 @Override 930 protected void onAttachedToWindow() { 931 super.onAttachedToWindow(); 932 mFirstLayout = true; 933 } 934 935 @Override 936 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 937 // For simple implementation, or internal size is always 0. 938 // We depend on the container to specify the layout size of 939 // our view. We can't really know what it is since we will be 940 // adding and removing different arbitrary views and do not 941 // want the layout to change as this happens. 942 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec), 943 getDefaultSize(0, heightMeasureSpec)); 944 945 // Children are just made to fill our space. 946 int childWidthSize = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); 947 int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); 948 949 /* 950 * Make sure all children have been properly measured. Decor views first. 951 * Right now we cheat and make this less complicated by assuming decor 952 * views won't intersect. We will pin to edges based on gravity. 953 */ 954 int size = getChildCount(); 955 for (int i = 0; i < size; ++i) { 956 final View child = getChildAt(i); 957 if (child.getVisibility() != GONE) { 958 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 959 if (lp != null && lp.isDecor) { 960 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 961 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 962 Log.d(TAG, "gravity: " + lp.gravity + " hgrav: " + hgrav + " vgrav: " + vgrav); 963 int widthMode = MeasureSpec.AT_MOST; 964 int heightMode = MeasureSpec.AT_MOST; 965 boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM; 966 boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT; 967 968 if (consumeVertical) { 969 widthMode = MeasureSpec.EXACTLY; 970 } else if (consumeHorizontal) { 971 heightMode = MeasureSpec.EXACTLY; 972 } 973 974 final int widthSpec = MeasureSpec.makeMeasureSpec(childWidthSize, widthMode); 975 final int heightSpec = MeasureSpec.makeMeasureSpec(childHeightSize, heightMode); 976 child.measure(widthSpec, heightSpec); 977 978 if (consumeVertical) { 979 childHeightSize -= child.getMeasuredHeight(); 980 } else if (consumeHorizontal) { 981 childWidthSize -= child.getMeasuredWidth(); 982 } 983 } 984 } 985 } 986 987 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY); 988 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY); 989 990 // Make sure we have created all fragments that we need to have shown. 991 mInLayout = true; 992 populate(); 993 mInLayout = false; 994 995 // Page views next. 996 size = getChildCount(); 997 for (int i = 0; i < size; ++i) { 998 final View child = getChildAt(i); 999 if (child.getVisibility() != GONE) { 1000 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child 1001 + ": " + mChildWidthMeasureSpec); 1002 1003 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1004 if (lp == null || !lp.isDecor) { 1005 child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec); 1006 } 1007 } 1008 } 1009 } 1010 1011 @Override 1012 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1013 super.onSizeChanged(w, h, oldw, oldh); 1014 1015 // Make sure scroll position is set correctly. 1016 if (w != oldw) { 1017 recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin); 1018 } 1019 } 1020 1021 private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) { 1022 final int widthWithMargin = width + margin; 1023 if (oldWidth > 0) { 1024 final int oldScrollPos = getScrollX(); 1025 final int oldwwm = oldWidth + oldMargin; 1026 final int oldScrollItem = oldScrollPos / oldwwm; 1027 final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm; 1028 final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin); 1029 scrollTo(scrollPos, getScrollY()); 1030 if (!mScroller.isFinished()) { 1031 // We now return to your regularly scheduled scroll, already in progress. 1032 final int newDuration = mScroller.getDuration() - mScroller.timePassed(); 1033 mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin, 0, newDuration); 1034 } 1035 } else { 1036 int scrollPos = mCurItem * widthWithMargin; 1037 if (scrollPos != getScrollX()) { 1038 completeScroll(); 1039 scrollTo(scrollPos, getScrollY()); 1040 } 1041 } 1042 } 1043 1044 @Override 1045 protected void onLayout(boolean changed, int l, int t, int r, int b) { 1046 mInLayout = true; 1047 populate(); 1048 mInLayout = false; 1049 1050 final int count = getChildCount(); 1051 int width = r - l; 1052 int height = b - t; 1053 int paddingLeft = getPaddingLeft(); 1054 int paddingTop = getPaddingTop(); 1055 int paddingRight = getPaddingRight(); 1056 int paddingBottom = getPaddingBottom(); 1057 final int scrollX = getScrollX(); 1058 1059 int decorCount = 0; 1060 1061 for (int i = 0; i < count; i++) { 1062 final View child = getChildAt(i); 1063 if (child.getVisibility() != GONE) { 1064 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1065 ItemInfo ii; 1066 int childLeft = 0; 1067 int childTop = 0; 1068 if (lp.isDecor) { 1069 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1070 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; 1071 switch (hgrav) { 1072 default: 1073 childLeft = paddingLeft; 1074 break; 1075 case Gravity.LEFT: 1076 childLeft = paddingLeft; 1077 paddingLeft += child.getMeasuredWidth(); 1078 break; 1079 case Gravity.CENTER_HORIZONTAL: 1080 childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1081 paddingLeft); 1082 break; 1083 case Gravity.RIGHT: 1084 childLeft = width - paddingRight - child.getMeasuredWidth(); 1085 paddingRight += child.getMeasuredWidth(); 1086 break; 1087 } 1088 switch (vgrav) { 1089 default: 1090 childTop = paddingTop; 1091 break; 1092 case Gravity.TOP: 1093 childTop = paddingTop; 1094 paddingTop += child.getMeasuredHeight(); 1095 break; 1096 case Gravity.CENTER_VERTICAL: 1097 childTop = Math.max((height - child.getMeasuredHeight()) / 2, 1098 paddingTop); 1099 break; 1100 case Gravity.BOTTOM: 1101 childTop = height - paddingBottom - child.getMeasuredHeight(); 1102 paddingBottom += child.getMeasuredHeight(); 1103 break; 1104 } 1105 childLeft += scrollX; 1106 decorCount++; 1107 child.layout(childLeft, childTop, 1108 childLeft + child.getMeasuredWidth(), 1109 childTop + child.getMeasuredHeight()); 1110 } else if ((ii = infoForChild(child)) != null) { 1111 int loff = (width + mPageMargin) * ii.position; 1112 childLeft = paddingLeft + loff; 1113 childTop = paddingTop; 1114 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object 1115 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth() 1116 + "x" + child.getMeasuredHeight()); 1117 child.layout(childLeft, childTop, 1118 childLeft + child.getMeasuredWidth(), 1119 childTop + child.getMeasuredHeight()); 1120 } 1121 } 1122 } 1123 mTopPageBounds = paddingTop; 1124 mBottomPageBounds = height - paddingBottom; 1125 mDecorChildCount = decorCount; 1126 mFirstLayout = false; 1127 } 1128 1129 @Override 1130 public void computeScroll() { 1131 if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished()); 1132 if (!mScroller.isFinished()) { 1133 if (mScroller.computeScrollOffset()) { 1134 if (DEBUG) Log.i(TAG, "computeScroll: still scrolling"); 1135 int oldX = getScrollX(); 1136 int oldY = getScrollY(); 1137 int x = mScroller.getCurrX(); 1138 int y = mScroller.getCurrY(); 1139 1140 if (oldX != x || oldY != y) { 1141 scrollTo(x, y); 1142 pageScrolled(x); 1143 } 1144 1145 // Keep on drawing until the animation has finished. 1146 invalidate(); 1147 return; 1148 } 1149 } 1150 1151 // Done with scroll, clean up state. 1152 completeScroll(); 1153 } 1154 1155 private void pageScrolled(int xpos) { 1156 final int widthWithMargin = getWidth() + mPageMargin; 1157 final int position = xpos / widthWithMargin; 1158 final int offsetPixels = xpos % widthWithMargin; 1159 final float offset = (float) offsetPixels / widthWithMargin; 1160 1161 mCalledSuper = false; 1162 onPageScrolled(position, offset, offsetPixels); 1163 if (!mCalledSuper) { 1164 throw new IllegalStateException( 1165 "onPageScrolled did not call superclass implementation"); 1166 } 1167 } 1168 1169 /** 1170 * This method will be invoked when the current page is scrolled, either as part 1171 * of a programmatically initiated smooth scroll or a user initiated touch scroll. 1172 * If you override this method you must call through to the superclass implementation 1173 * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled 1174 * returns. 1175 * 1176 * @param position Position index of the first page currently being displayed. 1177 * Page position+1 will be visible if positionOffset is nonzero. 1178 * @param offset Value from [0, 1) indicating the offset from the page at position. 1179 * @param offsetPixels Value in pixels indicating the offset from position. 1180 */ 1181 protected void onPageScrolled(int position, float offset, int offsetPixels) { 1182 // Offset any decor views if needed - keep them on-screen at all times. 1183 if (mDecorChildCount > 0) { 1184 final int scrollX = getScrollX(); 1185 int paddingLeft = getPaddingLeft(); 1186 int paddingRight = getPaddingRight(); 1187 final int width = getWidth(); 1188 final int childCount = getChildCount(); 1189 for (int i = 0; i < childCount; i++) { 1190 final View child = getChildAt(i); 1191 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 1192 if (!lp.isDecor) continue; 1193 1194 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; 1195 int childLeft = 0; 1196 switch (hgrav) { 1197 default: 1198 childLeft = paddingLeft; 1199 break; 1200 case Gravity.LEFT: 1201 childLeft = paddingLeft; 1202 paddingLeft += child.getWidth(); 1203 break; 1204 case Gravity.CENTER_HORIZONTAL: 1205 childLeft = Math.max((width - child.getMeasuredWidth()) / 2, 1206 paddingLeft); 1207 break; 1208 case Gravity.RIGHT: 1209 childLeft = width - paddingRight - child.getMeasuredWidth(); 1210 paddingRight += child.getMeasuredWidth(); 1211 break; 1212 } 1213 childLeft += scrollX; 1214 1215 final int childOffset = childLeft - child.getLeft(); 1216 if (childOffset != 0) { 1217 child.offsetLeftAndRight(childOffset); 1218 } 1219 } 1220 } 1221 1222 if (mOnPageChangeListener != null) { 1223 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1224 } 1225 if (mInternalPageChangeListener != null) { 1226 mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels); 1227 } 1228 mCalledSuper = true; 1229 } 1230 1231 private void completeScroll() { 1232 boolean needPopulate = mScrolling; 1233 if (needPopulate) { 1234 // Done with scroll, no longer want to cache view drawing. 1235 setScrollingCacheEnabled(false); 1236 mScroller.abortAnimation(); 1237 int oldX = getScrollX(); 1238 int oldY = getScrollY(); 1239 int x = mScroller.getCurrX(); 1240 int y = mScroller.getCurrY(); 1241 if (oldX != x || oldY != y) { 1242 scrollTo(x, y); 1243 } 1244 setScrollState(SCROLL_STATE_IDLE); 1245 } 1246 mPopulatePending = false; 1247 mScrolling = false; 1248 for (int i=0; i<mItems.size(); i++) { 1249 ItemInfo ii = mItems.get(i); 1250 if (ii.scrolling) { 1251 needPopulate = true; 1252 ii.scrolling = false; 1253 } 1254 } 1255 if (needPopulate) { 1256 populate(); 1257 } 1258 } 1259 1260 @Override 1261 public boolean onInterceptTouchEvent(MotionEvent ev) { 1262 /* 1263 * This method JUST determines whether we want to intercept the motion. 1264 * If we return true, onMotionEvent will be called and we do the actual 1265 * scrolling there. 1266 */ 1267 1268 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; 1269 1270 // Always take care of the touch gesture being complete. 1271 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 1272 // Release the drag. 1273 if (DEBUG) Log.v(TAG, "Intercept done!"); 1274 mIsBeingDragged = false; 1275 mIsUnableToDrag = false; 1276 mActivePointerId = INVALID_POINTER; 1277 if (mVelocityTracker != null) { 1278 mVelocityTracker.recycle(); 1279 mVelocityTracker = null; 1280 } 1281 return false; 1282 } 1283 1284 // Nothing more to do here if we have decided whether or not we 1285 // are dragging. 1286 if (action != MotionEvent.ACTION_DOWN) { 1287 if (mIsBeingDragged) { 1288 if (DEBUG) Log.v(TAG, "Intercept returning true!"); 1289 return true; 1290 } 1291 if (mIsUnableToDrag) { 1292 if (DEBUG) Log.v(TAG, "Intercept returning false!"); 1293 return false; 1294 } 1295 } 1296 1297 switch (action) { 1298 case MotionEvent.ACTION_MOVE: { 1299 /* 1300 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check 1301 * whether the user has moved far enough from his original down touch. 1302 */ 1303 1304 /* 1305 * Locally do absolute value. mLastMotionY is set to the y value 1306 * of the down event. 1307 */ 1308 final int activePointerId = mActivePointerId; 1309 if (activePointerId == INVALID_POINTER) { 1310 // If we don't have a valid id, the touch down wasn't on content. 1311 break; 1312 } 1313 1314 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); 1315 final float x = MotionEventCompat.getX(ev, pointerIndex); 1316 final float dx = x - mLastMotionX; 1317 final float xDiff = Math.abs(dx); 1318 final float y = MotionEventCompat.getY(ev, pointerIndex); 1319 final float yDiff = Math.abs(y - mLastMotionY); 1320 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 1321 1322 if (canScroll(this, false, (int) dx, (int) x, (int) y)) { 1323 // Nested view has scrollable area under this point. Let it be handled there. 1324 mInitialMotionX = mLastMotionX = x; 1325 mLastMotionY = y; 1326 return false; 1327 } 1328 if (xDiff > mTouchSlop && xDiff > yDiff) { 1329 if (DEBUG) Log.v(TAG, "Starting drag!"); 1330 mIsBeingDragged = true; 1331 setScrollState(SCROLL_STATE_DRAGGING); 1332 mLastMotionX = x; 1333 setScrollingCacheEnabled(true); 1334 } else { 1335 if (yDiff > mTouchSlop) { 1336 // The finger has moved enough in the vertical 1337 // direction to be counted as a drag... abort 1338 // any attempt to drag horizontally, to work correctly 1339 // with children that have scrolling containers. 1340 if (DEBUG) Log.v(TAG, "Starting unable to drag!"); 1341 mIsUnableToDrag = true; 1342 } 1343 } 1344 break; 1345 } 1346 1347 case MotionEvent.ACTION_DOWN: { 1348 /* 1349 * Remember location of down touch. 1350 * ACTION_DOWN always refers to pointer index 0. 1351 */ 1352 mLastMotionX = mInitialMotionX = ev.getX(); 1353 mLastMotionY = ev.getY(); 1354 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1355 1356 if (mScrollState == SCROLL_STATE_SETTLING) { 1357 // Let the user 'catch' the pager as it animates. 1358 mIsBeingDragged = true; 1359 mIsUnableToDrag = false; 1360 setScrollState(SCROLL_STATE_DRAGGING); 1361 } else { 1362 completeScroll(); 1363 mIsBeingDragged = false; 1364 mIsUnableToDrag = false; 1365 } 1366 1367 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY 1368 + " mIsBeingDragged=" + mIsBeingDragged 1369 + "mIsUnableToDrag=" + mIsUnableToDrag); 1370 break; 1371 } 1372 1373 case MotionEventCompat.ACTION_POINTER_UP: 1374 onSecondaryPointerUp(ev); 1375 break; 1376 } 1377 1378 if (!mIsBeingDragged) { 1379 // Track the velocity as long as we aren't dragging. 1380 // Once we start a real drag we will track in onTouchEvent. 1381 if (mVelocityTracker == null) { 1382 mVelocityTracker = VelocityTracker.obtain(); 1383 } 1384 mVelocityTracker.addMovement(ev); 1385 } 1386 1387 /* 1388 * The only time we want to intercept motion events is if we are in the 1389 * drag mode. 1390 */ 1391 return mIsBeingDragged; 1392 } 1393 1394 @Override 1395 public boolean onTouchEvent(MotionEvent ev) { 1396 if (mFakeDragging) { 1397 // A fake drag is in progress already, ignore this real one 1398 // but still eat the touch events. 1399 // (It is likely that the user is multi-touching the screen.) 1400 return true; 1401 } 1402 1403 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) { 1404 // Don't handle edge touches immediately -- they may actually belong to one of our 1405 // descendants. 1406 return false; 1407 } 1408 1409 if (mAdapter == null || mAdapter.getCount() == 0) { 1410 // Nothing to present or scroll; nothing to touch. 1411 return false; 1412 } 1413 1414 if (mVelocityTracker == null) { 1415 mVelocityTracker = VelocityTracker.obtain(); 1416 } 1417 mVelocityTracker.addMovement(ev); 1418 1419 final int action = ev.getAction(); 1420 boolean needsInvalidate = false; 1421 1422 switch (action & MotionEventCompat.ACTION_MASK) { 1423 case MotionEvent.ACTION_DOWN: { 1424 /* 1425 * If being flinged and user touches, stop the fling. isFinished 1426 * will be false if being flinged. 1427 */ 1428 completeScroll(); 1429 1430 // Remember where the motion event started 1431 mLastMotionX = mInitialMotionX = ev.getX(); 1432 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); 1433 break; 1434 } 1435 case MotionEvent.ACTION_MOVE: 1436 if (!mIsBeingDragged) { 1437 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1438 final float x = MotionEventCompat.getX(ev, pointerIndex); 1439 final float xDiff = Math.abs(x - mLastMotionX); 1440 final float y = MotionEventCompat.getY(ev, pointerIndex); 1441 final float yDiff = Math.abs(y - mLastMotionY); 1442 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); 1443 if (xDiff > mTouchSlop && xDiff > yDiff) { 1444 if (DEBUG) Log.v(TAG, "Starting drag!"); 1445 mIsBeingDragged = true; 1446 mLastMotionX = x; 1447 setScrollState(SCROLL_STATE_DRAGGING); 1448 setScrollingCacheEnabled(true); 1449 } 1450 } 1451 if (mIsBeingDragged) { 1452 // Scroll to follow the motion event 1453 final int activePointerIndex = MotionEventCompat.findPointerIndex( 1454 ev, mActivePointerId); 1455 final float x = MotionEventCompat.getX(ev, activePointerIndex); 1456 final float deltaX = mLastMotionX - x; 1457 mLastMotionX = x; 1458 float oldScrollX = getScrollX(); 1459 float scrollX = oldScrollX + deltaX; 1460 final int width = getWidth(); 1461 final int widthWithMargin = width + mPageMargin; 1462 1463 final int lastItemIndex = mAdapter.getCount() - 1; 1464 final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin); 1465 final float rightBound = 1466 Math.min(mCurItem + 1, lastItemIndex) * widthWithMargin; 1467 if (scrollX < leftBound) { 1468 if (leftBound == 0) { 1469 float over = -scrollX; 1470 needsInvalidate = mLeftEdge.onPull(over / width); 1471 } 1472 scrollX = leftBound; 1473 } else if (scrollX > rightBound) { 1474 if (rightBound == lastItemIndex * widthWithMargin) { 1475 float over = scrollX - rightBound; 1476 needsInvalidate = mRightEdge.onPull(over / width); 1477 } 1478 scrollX = rightBound; 1479 } 1480 // Don't lose the rounded component 1481 mLastMotionX += scrollX - (int) scrollX; 1482 scrollTo((int) scrollX, getScrollY()); 1483 pageScrolled((int) scrollX); 1484 } 1485 break; 1486 case MotionEvent.ACTION_UP: 1487 if (mIsBeingDragged) { 1488 final VelocityTracker velocityTracker = mVelocityTracker; 1489 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1490 int initialVelocity = (int) VelocityTrackerCompat.getXVelocity( 1491 velocityTracker, mActivePointerId); 1492 mPopulatePending = true; 1493 final int widthWithMargin = getWidth() + mPageMargin; 1494 final int scrollX = getScrollX(); 1495 final int currentPage = scrollX / widthWithMargin; 1496 final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin; 1497 final int activePointerIndex = 1498 MotionEventCompat.findPointerIndex(ev, mActivePointerId); 1499 final float x = MotionEventCompat.getX(ev, activePointerIndex); 1500 final int totalDelta = (int) (x - mInitialMotionX); 1501 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, 1502 totalDelta); 1503 setCurrentItemInternal(nextPage, true, true, initialVelocity); 1504 1505 mActivePointerId = INVALID_POINTER; 1506 endDrag(); 1507 needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1508 } 1509 break; 1510 case MotionEvent.ACTION_CANCEL: 1511 if (mIsBeingDragged) { 1512 setCurrentItemInternal(mCurItem, true, true); 1513 mActivePointerId = INVALID_POINTER; 1514 endDrag(); 1515 needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease(); 1516 } 1517 break; 1518 case MotionEventCompat.ACTION_POINTER_DOWN: { 1519 final int index = MotionEventCompat.getActionIndex(ev); 1520 final float x = MotionEventCompat.getX(ev, index); 1521 mLastMotionX = x; 1522 mActivePointerId = MotionEventCompat.getPointerId(ev, index); 1523 break; 1524 } 1525 case MotionEventCompat.ACTION_POINTER_UP: 1526 onSecondaryPointerUp(ev); 1527 mLastMotionX = MotionEventCompat.getX(ev, 1528 MotionEventCompat.findPointerIndex(ev, mActivePointerId)); 1529 break; 1530 } 1531 if (needsInvalidate) { 1532 invalidate(); 1533 } 1534 return true; 1535 } 1536 1537 private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) { 1538 int targetPage; 1539 if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) { 1540 targetPage = velocity > 0 ? currentPage : currentPage + 1; 1541 } else { 1542 targetPage = (int) (currentPage + pageOffset + 0.5f); 1543 } 1544 1545 return targetPage; 1546 } 1547 1548 @Override 1549 public void draw(Canvas canvas) { 1550 super.draw(canvas); 1551 boolean needsInvalidate = false; 1552 1553 final int overScrollMode = ViewCompat.getOverScrollMode(this); 1554 if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || 1555 (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && 1556 mAdapter != null && mAdapter.getCount() > 1)) { 1557 if (!mLeftEdge.isFinished()) { 1558 final int restoreCount = canvas.save(); 1559 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1560 1561 canvas.rotate(270); 1562 canvas.translate(-height + getPaddingTop(), 0); 1563 mLeftEdge.setSize(height, getWidth()); 1564 needsInvalidate |= mLeftEdge.draw(canvas); 1565 canvas.restoreToCount(restoreCount); 1566 } 1567 if (!mRightEdge.isFinished()) { 1568 final int restoreCount = canvas.save(); 1569 final int width = getWidth(); 1570 final int height = getHeight() - getPaddingTop() - getPaddingBottom(); 1571 final int itemCount = mAdapter != null ? mAdapter.getCount() : 1; 1572 1573 canvas.rotate(90); 1574 canvas.translate(-getPaddingTop(), 1575 -itemCount * (width + mPageMargin) + mPageMargin); 1576 mRightEdge.setSize(height, width); 1577 needsInvalidate |= mRightEdge.draw(canvas); 1578 canvas.restoreToCount(restoreCount); 1579 } 1580 } else { 1581 mLeftEdge.finish(); 1582 mRightEdge.finish(); 1583 } 1584 1585 if (needsInvalidate) { 1586 // Keep animating 1587 invalidate(); 1588 } 1589 } 1590 1591 @Override 1592 protected void onDraw(Canvas canvas) { 1593 super.onDraw(canvas); 1594 1595 // Draw the margin drawable if needed. 1596 if (mPageMargin > 0 && mMarginDrawable != null) { 1597 final int scrollX = getScrollX(); 1598 final int width = getWidth(); 1599 final int offset = scrollX % (width + mPageMargin); 1600 if (offset != 0) { 1601 // Pages fit completely when settled; we only need to draw when in between 1602 final int left = scrollX - offset + width; 1603 mMarginDrawable.setBounds(left, mTopPageBounds, left + mPageMargin, 1604 mBottomPageBounds); 1605 mMarginDrawable.draw(canvas); 1606 } 1607 } 1608 } 1609 1610 /** 1611 * Start a fake drag of the pager. 1612 * 1613 * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager 1614 * with the touch scrolling of another view, while still letting the ViewPager 1615 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.) 1616 * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call 1617 * {@link #endFakeDrag()} to complete the fake drag and fling as necessary. 1618 * 1619 * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag 1620 * is already in progress, this method will return false. 1621 * 1622 * @return true if the fake drag began successfully, false if it could not be started. 1623 * 1624 * @see #fakeDragBy(float) 1625 * @see #endFakeDrag() 1626 */ 1627 public boolean beginFakeDrag() { 1628 if (mIsBeingDragged) { 1629 return false; 1630 } 1631 mFakeDragging = true; 1632 setScrollState(SCROLL_STATE_DRAGGING); 1633 mInitialMotionX = mLastMotionX = 0; 1634 if (mVelocityTracker == null) { 1635 mVelocityTracker = VelocityTracker.obtain(); 1636 } else { 1637 mVelocityTracker.clear(); 1638 } 1639 final long time = SystemClock.uptimeMillis(); 1640 final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0); 1641 mVelocityTracker.addMovement(ev); 1642 ev.recycle(); 1643 mFakeDragBeginTime = time; 1644 return true; 1645 } 1646 1647 /** 1648 * End a fake drag of the pager. 1649 * 1650 * @see #beginFakeDrag() 1651 * @see #fakeDragBy(float) 1652 */ 1653 public void endFakeDrag() { 1654 if (!mFakeDragging) { 1655 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 1656 } 1657 1658 final VelocityTracker velocityTracker = mVelocityTracker; 1659 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); 1660 int initialVelocity = (int)VelocityTrackerCompat.getYVelocity( 1661 velocityTracker, mActivePointerId); 1662 mPopulatePending = true; 1663 final int totalDelta = (int) (mLastMotionX - mInitialMotionX); 1664 final int scrollX = getScrollX(); 1665 final int widthWithMargin = getWidth() + mPageMargin; 1666 final int currentPage = scrollX / widthWithMargin; 1667 final float pageOffset = (float) (scrollX % widthWithMargin) / widthWithMargin; 1668 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity, totalDelta); 1669 setCurrentItemInternal(nextPage, true, true, initialVelocity); 1670 endDrag(); 1671 1672 mFakeDragging = false; 1673 } 1674 1675 /** 1676 * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first. 1677 * 1678 * @param xOffset Offset in pixels to drag by. 1679 * @see #beginFakeDrag() 1680 * @see #endFakeDrag() 1681 */ 1682 public void fakeDragBy(float xOffset) { 1683 if (!mFakeDragging) { 1684 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first."); 1685 } 1686 1687 mLastMotionX += xOffset; 1688 float scrollX = getScrollX() - xOffset; 1689 final int width = getWidth(); 1690 final int widthWithMargin = width + mPageMargin; 1691 1692 final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin); 1693 final float rightBound = 1694 Math.min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin; 1695 if (scrollX < leftBound) { 1696 scrollX = leftBound; 1697 } else if (scrollX > rightBound) { 1698 scrollX = rightBound; 1699 } 1700 // Don't lose the rounded component 1701 mLastMotionX += scrollX - (int) scrollX; 1702 scrollTo((int) scrollX, getScrollY()); 1703 pageScrolled((int) scrollX); 1704 1705 // Synthesize an event for the VelocityTracker. 1706 final long time = SystemClock.uptimeMillis(); 1707 final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE, 1708 mLastMotionX, 0, 0); 1709 mVelocityTracker.addMovement(ev); 1710 ev.recycle(); 1711 } 1712 1713 /** 1714 * Returns true if a fake drag is in progress. 1715 * 1716 * @return true if currently in a fake drag, false otherwise. 1717 * 1718 * @see #beginFakeDrag() 1719 * @see #fakeDragBy(float) 1720 * @see #endFakeDrag() 1721 */ 1722 public boolean isFakeDragging() { 1723 return mFakeDragging; 1724 } 1725 1726 private void onSecondaryPointerUp(MotionEvent ev) { 1727 final int pointerIndex = MotionEventCompat.getActionIndex(ev); 1728 final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); 1729 if (pointerId == mActivePointerId) { 1730 // This was our active pointer going up. Choose a new 1731 // active pointer and adjust accordingly. 1732 final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 1733 mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex); 1734 mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); 1735 if (mVelocityTracker != null) { 1736 mVelocityTracker.clear(); 1737 } 1738 } 1739 } 1740 1741 private void endDrag() { 1742 mIsBeingDragged = false; 1743 mIsUnableToDrag = false; 1744 1745 if (mVelocityTracker != null) { 1746 mVelocityTracker.recycle(); 1747 mVelocityTracker = null; 1748 } 1749 } 1750 1751 private void setScrollingCacheEnabled(boolean enabled) { 1752 if (mScrollingCacheEnabled != enabled) { 1753 mScrollingCacheEnabled = enabled; 1754 if (USE_CACHE) { 1755 final int size = getChildCount(); 1756 for (int i = 0; i < size; ++i) { 1757 final View child = getChildAt(i); 1758 if (child.getVisibility() != GONE) { 1759 child.setDrawingCacheEnabled(enabled); 1760 } 1761 } 1762 } 1763 } 1764 } 1765 1766 /** 1767 * Tests scrollability within child views of v given a delta of dx. 1768 * 1769 * @param v View to test for horizontal scrollability 1770 * @param checkV Whether the view v passed should itself be checked for scrollability (true), 1771 * or just its children (false). 1772 * @param dx Delta scrolled in pixels 1773 * @param x X coordinate of the active touch point 1774 * @param y Y coordinate of the active touch point 1775 * @return true if child views of v can be scrolled by delta of dx. 1776 */ 1777 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) { 1778 if (v instanceof ViewGroup) { 1779 final ViewGroup group = (ViewGroup) v; 1780 final int scrollX = v.getScrollX(); 1781 final int scrollY = v.getScrollY(); 1782 final int count = group.getChildCount(); 1783 // Count backwards - let topmost views consume scroll distance first. 1784 for (int i = count - 1; i >= 0; i--) { 1785 // TODO: Add versioned support here for transformed views. 1786 // This will not work for transformed views in Honeycomb+ 1787 final View child = group.getChildAt(i); 1788 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && 1789 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && 1790 canScroll(child, true, dx, x + scrollX - child.getLeft(), 1791 y + scrollY - child.getTop())) { 1792 return true; 1793 } 1794 } 1795 } 1796 1797 return checkV && ViewCompat.canScrollHorizontally(v, -dx); 1798 } 1799 1800 @Override 1801 public boolean dispatchKeyEvent(KeyEvent event) { 1802 // Let the focused view and/or our descendants get the key first 1803 return super.dispatchKeyEvent(event) || executeKeyEvent(event); 1804 } 1805 1806 /** 1807 * You can call this function yourself to have the scroll view perform 1808 * scrolling from a key event, just as if the event had been dispatched to 1809 * it by the view hierarchy. 1810 * 1811 * @param event The key event to execute. 1812 * @return Return true if the event was handled, else false. 1813 */ 1814 public boolean executeKeyEvent(KeyEvent event) { 1815 boolean handled = false; 1816 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1817 switch (event.getKeyCode()) { 1818 case KeyEvent.KEYCODE_DPAD_LEFT: 1819 handled = arrowScroll(FOCUS_LEFT); 1820 break; 1821 case KeyEvent.KEYCODE_DPAD_RIGHT: 1822 handled = arrowScroll(FOCUS_RIGHT); 1823 break; 1824 case KeyEvent.KEYCODE_TAB: 1825 if (Build.VERSION.SDK_INT >= 11) { 1826 // The focus finder had a bug handling FOCUS_FORWARD and FOCUS_BACKWARD 1827 // before Android 3.0. Ignore the tab key on those devices. 1828 if (KeyEventCompat.hasNoModifiers(event)) { 1829 handled = arrowScroll(FOCUS_FORWARD); 1830 } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) { 1831 handled = arrowScroll(FOCUS_BACKWARD); 1832 } 1833 } 1834 break; 1835 } 1836 } 1837 return handled; 1838 } 1839 1840 public boolean arrowScroll(int direction) { 1841 View currentFocused = findFocus(); 1842 if (currentFocused == this) currentFocused = null; 1843 1844 boolean handled = false; 1845 1846 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, 1847 direction); 1848 if (nextFocused != null && nextFocused != currentFocused) { 1849 if (direction == View.FOCUS_LEFT) { 1850 // If there is nothing to the left, or this is causing us to 1851 // jump to the right, then what we really want to do is page left. 1852 if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) { 1853 handled = pageLeft(); 1854 } else { 1855 handled = nextFocused.requestFocus(); 1856 } 1857 } else if (direction == View.FOCUS_RIGHT) { 1858 // If there is nothing to the right, or this is causing us to 1859 // jump to the left, then what we really want to do is page right. 1860 if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) { 1861 handled = pageRight(); 1862 } else { 1863 handled = nextFocused.requestFocus(); 1864 } 1865 } 1866 } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) { 1867 // Trying to move left and nothing there; try to page. 1868 handled = pageLeft(); 1869 } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) { 1870 // Trying to move right and nothing there; try to page. 1871 handled = pageRight(); 1872 } 1873 if (handled) { 1874 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); 1875 } 1876 return handled; 1877 } 1878 1879 boolean pageLeft() { 1880 if (mCurItem > 0) { 1881 setCurrentItem(mCurItem-1, true); 1882 return true; 1883 } 1884 return false; 1885 } 1886 1887 boolean pageRight() { 1888 if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) { 1889 setCurrentItem(mCurItem+1, true); 1890 return true; 1891 } 1892 return false; 1893 } 1894 1895 /** 1896 * We only want the current page that is being shown to be focusable. 1897 */ 1898 @Override 1899 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) { 1900 final int focusableCount = views.size(); 1901 1902 final int descendantFocusability = getDescendantFocusability(); 1903 1904 if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { 1905 for (int i = 0; i < getChildCount(); i++) { 1906 final View child = getChildAt(i); 1907 if (child.getVisibility() == VISIBLE) { 1908 ItemInfo ii = infoForChild(child); 1909 if (ii != null && ii.position == mCurItem) { 1910 child.addFocusables(views, direction, focusableMode); 1911 } 1912 } 1913 } 1914 } 1915 1916 // we add ourselves (if focusable) in all cases except for when we are 1917 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is 1918 // to avoid the focus search finding layouts when a more precise search 1919 // among the focusable children would be more interesting. 1920 if ( 1921 descendantFocusability != FOCUS_AFTER_DESCENDANTS || 1922 // No focusable descendants 1923 (focusableCount == views.size())) { 1924 // Note that we can't call the superclass here, because it will 1925 // add all views in. So we need to do the same thing View does. 1926 if (!isFocusable()) { 1927 return; 1928 } 1929 if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE && 1930 isInTouchMode() && !isFocusableInTouchMode()) { 1931 return; 1932 } 1933 if (views != null) { 1934 views.add(this); 1935 } 1936 } 1937 } 1938 1939 /** 1940 * We only want the current page that is being shown to be touchable. 1941 */ 1942 @Override 1943 public void addTouchables(ArrayList<View> views) { 1944 // Note that we don't call super.addTouchables(), which means that 1945 // we don't call View.addTouchables(). This is okay because a ViewPager 1946 // is itself not touchable. 1947 for (int i = 0; i < getChildCount(); i++) { 1948 final View child = getChildAt(i); 1949 if (child.getVisibility() == VISIBLE) { 1950 ItemInfo ii = infoForChild(child); 1951 if (ii != null && ii.position == mCurItem) { 1952 child.addTouchables(views); 1953 } 1954 } 1955 } 1956 } 1957 1958 /** 1959 * We only want the current page that is being shown to be focusable. 1960 */ 1961 @Override 1962 protected boolean onRequestFocusInDescendants(int direction, 1963 Rect previouslyFocusedRect) { 1964 int index; 1965 int increment; 1966 int end; 1967 int count = getChildCount(); 1968 if ((direction & FOCUS_FORWARD) != 0) { 1969 index = 0; 1970 increment = 1; 1971 end = count; 1972 } else { 1973 index = count - 1; 1974 increment = -1; 1975 end = -1; 1976 } 1977 for (int i = index; i != end; i += increment) { 1978 View child = getChildAt(i); 1979 if (child.getVisibility() == VISIBLE) { 1980 ItemInfo ii = infoForChild(child); 1981 if (ii != null && ii.position == mCurItem) { 1982 if (child.requestFocus(direction, previouslyFocusedRect)) { 1983 return true; 1984 } 1985 } 1986 } 1987 } 1988 return false; 1989 } 1990 1991 @Override 1992 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { 1993 // ViewPagers should only report accessibility info for the current page, 1994 // otherwise things get very confusing. 1995 1996 // TODO: Should this note something about the paging container? 1997 1998 final int childCount = getChildCount(); 1999 for (int i = 0; i < childCount; i++) { 2000 final View child = getChildAt(i); 2001 if (child.getVisibility() == VISIBLE) { 2002 final ItemInfo ii = infoForChild(child); 2003 if (ii != null && ii.position == mCurItem && 2004 child.dispatchPopulateAccessibilityEvent(event)) { 2005 return true; 2006 } 2007 } 2008 } 2009 2010 return false; 2011 } 2012 2013 @Override 2014 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 2015 return new LayoutParams(); 2016 } 2017 2018 @Override 2019 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 2020 return generateDefaultLayoutParams(); 2021 } 2022 2023 @Override 2024 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 2025 return p instanceof LayoutParams && super.checkLayoutParams(p); 2026 } 2027 2028 @Override 2029 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 2030 return new LayoutParams(getContext(), attrs); 2031 } 2032 2033 private class PagerObserver extends DataSetObserver { 2034 @Override 2035 public void onChanged() { 2036 dataSetChanged(); 2037 } 2038 @Override 2039 public void onInvalidated() { 2040 dataSetChanged(); 2041 } 2042 } 2043 2044 public static class LayoutParams extends ViewGroup.LayoutParams { 2045 /** 2046 * true if this view is a decoration on the pager itself and not 2047 * a view supplied by the adapter. 2048 */ 2049 public boolean isDecor; 2050 2051 public int gravity; 2052 2053 public LayoutParams() { 2054 super(FILL_PARENT, FILL_PARENT); 2055 } 2056 2057 public LayoutParams(Context context, AttributeSet attrs) { 2058 super(context, attrs); 2059 2060 final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS); 2061 gravity = a.getInteger(0, Gravity.NO_GRAVITY); 2062 a.recycle(); 2063 } 2064 } 2065} 2066