blob: 80189428e54f81cbfb0f1fcaeb0dc0cce91cefbb [file] [log] [blame]
Alan Viverette75fd8f92015-03-09 15:51:10 -07001/*
2 * Copyright (C) 2015 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.internal.widget;
18
19import android.annotation.DrawableRes;
20import android.content.Context;
21import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.database.DataSetObserver;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.graphics.drawable.Drawable;
27import android.os.Bundle;
28import android.os.Parcel;
29import android.os.Parcelable;
30import android.os.SystemClock;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.view.FocusFinder;
34import android.view.Gravity;
35import android.view.KeyEvent;
36import android.view.MotionEvent;
37import android.view.SoundEffectConstants;
38import android.view.VelocityTracker;
39import android.view.View;
40import android.view.ViewConfiguration;
41import android.view.ViewGroup;
42import android.view.ViewParent;
43import android.view.accessibility.AccessibilityEvent;
44import android.view.accessibility.AccessibilityNodeInfo;
45import android.view.accessibility.AccessibilityRecord;
46import android.view.animation.Interpolator;
47import android.widget.EdgeEffect;
48import android.widget.Scroller;
49
50import java.util.ArrayList;
51import java.util.Collections;
52import java.util.Comparator;
53
54/**
55 * Layout manager that allows the user to flip left and right
56 * through pages of data. You supply an implementation of a
57 * {@link android.support.v4.view.PagerAdapter} to generate the pages that the view shows.
58 *
59 * <p>Note this class is currently under early design and
60 * development. The API will likely change in later updates of
61 * the compatibility library, requiring changes to the source code
62 * of apps when they are compiled against the newer version.</p>
63 *
64 * <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
65 * which is a convenient way to supply and manage the lifecycle of each page.
66 * There are standard adapters implemented for using fragments with the ViewPager,
67 * which cover the most common use cases. These are
68 * {@link android.support.v4.app.FragmentPagerAdapter} and
69 * {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
70 * classes have simple code showing how to build a full user interface
71 * with them.
72 *
73 * <p>For more information about how to use ViewPager, read <a
74 * href="{@docRoot}training/implementing-navigation/lateral.html">Creating Swipe Views with
75 * Tabs</a>.</p>
76 *
77 * <p>Below is a more complicated example of ViewPager, using it in conjunction
78 * with {@link android.app.ActionBar} tabs. You can find other examples of using
79 * ViewPager in the API 4+ Support Demos and API 13+ Support Demos sample code.
80 *
81 * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java
82 * complete}
83 */
84public class ViewPager extends ViewGroup {
85 private static final String TAG = "ViewPager";
86 private static final boolean DEBUG = false;
87
88 private static final boolean USE_CACHE = false;
89
90 private static final int DEFAULT_OFFSCREEN_PAGES = 1;
91 private static final int MAX_SETTLE_DURATION = 600; // ms
92 private static final int MIN_DISTANCE_FOR_FLING = 25; // dips
93
94 private static final int DEFAULT_GUTTER_SIZE = 16; // dips
95
96 private static final int MIN_FLING_VELOCITY = 400; // dips
97
98 private static final int[] LAYOUT_ATTRS = new int[] {
99 com.android.internal.R.attr.layout_gravity
100 };
101
102 /**
103 * Used to track what the expected number of items in the adapter should be.
104 * If the app changes this when we don't expect it, we'll throw a big obnoxious exception.
105 */
106 private int mExpectedAdapterCount;
107
108 static class ItemInfo {
109 Object object;
110 int position;
111 boolean scrolling;
112 float widthFactor;
113 float offset;
114 }
115
116 private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
117 @Override
118 public int compare(ItemInfo lhs, ItemInfo rhs) {
119 return lhs.position - rhs.position;
120 }
121 };
122
123 private static final Interpolator sInterpolator = new Interpolator() {
124 public float getInterpolation(float t) {
125 t -= 1.0f;
126 return t * t * t * t * t + 1.0f;
127 }
128 };
129
130 private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
131 private final ItemInfo mTempItem = new ItemInfo();
132
133 private final Rect mTempRect = new Rect();
134
135 private PagerAdapter mAdapter;
136 private int mCurItem; // Index of currently displayed page.
137 private int mRestoredCurItem = -1;
138 private Parcelable mRestoredAdapterState = null;
139 private ClassLoader mRestoredClassLoader = null;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700140 private final Scroller mScroller;
Alan Viverette75fd8f92015-03-09 15:51:10 -0700141 private PagerObserver mObserver;
142
143 private int mPageMargin;
144 private Drawable mMarginDrawable;
145 private int mTopPageBounds;
146 private int mBottomPageBounds;
147
148 // Offsets of the first and last items, if known.
149 // Set during population, used to determine if we are at the beginning
150 // or end of the pager data set during touch scrolling.
151 private float mFirstOffset = -Float.MAX_VALUE;
152 private float mLastOffset = Float.MAX_VALUE;
153
154 private int mChildWidthMeasureSpec;
155 private int mChildHeightMeasureSpec;
156 private boolean mInLayout;
157
158 private boolean mScrollingCacheEnabled;
159
160 private boolean mPopulatePending;
161 private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
162
163 private boolean mIsBeingDragged;
164 private boolean mIsUnableToDrag;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700165 private final int mDefaultGutterSize;
Alan Viverette75fd8f92015-03-09 15:51:10 -0700166 private int mGutterSize;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700167 private final int mTouchSlop;
Alan Viverette75fd8f92015-03-09 15:51:10 -0700168 /**
169 * Position of the last motion event.
170 */
171 private float mLastMotionX;
172 private float mLastMotionY;
173 private float mInitialMotionX;
174 private float mInitialMotionY;
175 /**
176 * ID of the active pointer. This is used to retain consistency during
177 * drags/flings if multiple pointers are used.
178 */
179 private int mActivePointerId = INVALID_POINTER;
180 /**
181 * Sentinel value for no current active pointer.
182 * Used by {@link #mActivePointerId}.
183 */
184 private static final int INVALID_POINTER = -1;
185
186 /**
187 * Determines speed during touch scrolling
188 */
189 private VelocityTracker mVelocityTracker;
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700190 private final int mMinimumVelocity;
191 private final int mMaximumVelocity;
192 private final int mFlingDistance;
193 private final int mCloseEnough;
Alan Viverette75fd8f92015-03-09 15:51:10 -0700194
195 // If the pager is at least this close to its final position, complete the scroll
196 // on touch down and let the user interact with the content inside instead of
197 // "catching" the flinging pager.
198 private static final int CLOSE_ENOUGH = 2; // dp
199
200 private boolean mFakeDragging;
201 private long mFakeDragBeginTime;
202
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700203 private final EdgeEffect mLeftEdge;
204 private final EdgeEffect mRightEdge;
Alan Viverette75fd8f92015-03-09 15:51:10 -0700205
206 private boolean mFirstLayout = true;
207 private boolean mNeedCalculatePageOffsets = false;
208 private boolean mCalledSuper;
209 private int mDecorChildCount;
210
211 private OnPageChangeListener mOnPageChangeListener;
212 private OnPageChangeListener mInternalPageChangeListener;
213 private OnAdapterChangeListener mAdapterChangeListener;
214 private PageTransformer mPageTransformer;
215
216 private static final int DRAW_ORDER_DEFAULT = 0;
217 private static final int DRAW_ORDER_FORWARD = 1;
218 private static final int DRAW_ORDER_REVERSE = 2;
219 private int mDrawingOrder;
220 private ArrayList<View> mDrawingOrderedChildren;
221 private static final ViewPositionComparator sPositionComparator = new ViewPositionComparator();
222
223 /**
224 * Indicates that the pager is in an idle, settled state. The current page
225 * is fully in view and no animation is in progress.
226 */
227 public static final int SCROLL_STATE_IDLE = 0;
228
229 /**
230 * Indicates that the pager is currently being dragged by the user.
231 */
232 public static final int SCROLL_STATE_DRAGGING = 1;
233
234 /**
235 * Indicates that the pager is in the process of settling to a final position.
236 */
237 public static final int SCROLL_STATE_SETTLING = 2;
238
239 private final Runnable mEndScrollRunnable = new Runnable() {
240 public void run() {
241 setScrollState(SCROLL_STATE_IDLE);
242 populate();
243 }
244 };
245
246 private int mScrollState = SCROLL_STATE_IDLE;
247
248 /**
249 * Callback interface for responding to changing state of the selected page.
250 */
251 public interface OnPageChangeListener {
252
253 /**
254 * This method will be invoked when the current page is scrolled, either as part
255 * of a programmatically initiated smooth scroll or a user initiated touch scroll.
256 *
257 * @param position Position index of the first page currently being displayed.
258 * Page position+1 will be visible if positionOffset is nonzero.
259 * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
260 * @param positionOffsetPixels Value in pixels indicating the offset from position.
261 */
262 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
263
264 /**
265 * This method will be invoked when a new page becomes selected. Animation is not
266 * necessarily complete.
267 *
268 * @param position Position index of the new selected page.
269 */
270 public void onPageSelected(int position);
271
272 /**
273 * Called when the scroll state changes. Useful for discovering when the user
274 * begins dragging, when the pager is automatically settling to the current page,
275 * or when it is fully stopped/idle.
276 *
277 * @param state The new scroll state.
278 * @see com.android.internal.widget.ViewPager#SCROLL_STATE_IDLE
279 * @see com.android.internal.widget.ViewPager#SCROLL_STATE_DRAGGING
280 * @see com.android.internal.widget.ViewPager#SCROLL_STATE_SETTLING
281 */
282 public void onPageScrollStateChanged(int state);
283 }
284
285 /**
286 * Simple implementation of the {@link OnPageChangeListener} interface with stub
287 * implementations of each method. Extend this if you do not intend to override
288 * every method of {@link OnPageChangeListener}.
289 */
290 public static class SimpleOnPageChangeListener implements OnPageChangeListener {
291 @Override
292 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
293 // This space for rent
294 }
295
296 @Override
297 public void onPageSelected(int position) {
298 // This space for rent
299 }
300
301 @Override
302 public void onPageScrollStateChanged(int state) {
303 // This space for rent
304 }
305 }
306
307 /**
308 * A PageTransformer is invoked whenever a visible/attached page is scrolled.
309 * This offers an opportunity for the application to apply a custom transformation
310 * to the page views using animation properties.
311 *
312 * <p>As property animation is only supported as of Android 3.0 and forward,
313 * setting a PageTransformer on a ViewPager on earlier platform versions will
314 * be ignored.</p>
315 */
316 public interface PageTransformer {
317 /**
318 * Apply a property transformation to the given page.
319 *
320 * @param page Apply the transformation to this page
321 * @param position Position of page relative to the current front-and-center
322 * position of the pager. 0 is front and center. 1 is one full
323 * page position to the right, and -1 is one page position to the left.
324 */
325 public void transformPage(View page, float position);
326 }
327
328 /**
329 * Used internally to monitor when adapters are switched.
330 */
331 interface OnAdapterChangeListener {
332 public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
333 }
334
335 /**
336 * Used internally to tag special types of child views that should be added as
337 * pager decorations by default.
338 */
339 interface Decor {}
340
341 public ViewPager(Context context) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700342 this(context, null);
Alan Viverette75fd8f92015-03-09 15:51:10 -0700343 }
344
345 public ViewPager(Context context, AttributeSet attrs) {
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700346 this(context, attrs, 0);
Alan Viverette75fd8f92015-03-09 15:51:10 -0700347 }
348
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700349 public ViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
350 this(context, attrs, defStyleAttr, 0);
351 }
352
353 public ViewPager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
354 super(context, attrs, defStyleAttr, defStyleRes);
355
Alan Viverette75fd8f92015-03-09 15:51:10 -0700356 setWillNotDraw(false);
357 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
358 setFocusable(true);
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700359
Alan Viverette75fd8f92015-03-09 15:51:10 -0700360 mScroller = new Scroller(context, sInterpolator);
361 final ViewConfiguration configuration = ViewConfiguration.get(context);
362 final float density = context.getResources().getDisplayMetrics().density;
363
364 mTouchSlop = configuration.getScaledPagingTouchSlop();
365 mMinimumVelocity = (int) (MIN_FLING_VELOCITY * density);
366 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
367 mLeftEdge = new EdgeEffect(context);
368 mRightEdge = new EdgeEffect(context);
369
370 mFlingDistance = (int) (MIN_DISTANCE_FOR_FLING * density);
371 mCloseEnough = (int) (CLOSE_ENOUGH * density);
372 mDefaultGutterSize = (int) (DEFAULT_GUTTER_SIZE * density);
373
374 setAccessibilityDelegate(new MyAccessibilityDelegate());
375
376 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
377 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
378 }
379 }
380
381 @Override
382 protected void onDetachedFromWindow() {
383 removeCallbacks(mEndScrollRunnable);
384 super.onDetachedFromWindow();
385 }
386
387 private void setScrollState(int newState) {
388 if (mScrollState == newState) {
389 return;
390 }
391
392 mScrollState = newState;
393 if (mPageTransformer != null) {
394 // PageTransformers can do complex things that benefit from hardware layers.
395 enableLayers(newState != SCROLL_STATE_IDLE);
396 }
397 if (mOnPageChangeListener != null) {
398 mOnPageChangeListener.onPageScrollStateChanged(newState);
399 }
400 }
401
402 /**
403 * Set a PagerAdapter that will supply views for this pager as needed.
404 *
405 * @param adapter Adapter to use
406 */
407 public void setAdapter(PagerAdapter adapter) {
408 if (mAdapter != null) {
409 mAdapter.unregisterDataSetObserver(mObserver);
410 mAdapter.startUpdate(this);
411 for (int i = 0; i < mItems.size(); i++) {
412 final ItemInfo ii = mItems.get(i);
413 mAdapter.destroyItem(this, ii.position, ii.object);
414 }
415 mAdapter.finishUpdate(this);
416 mItems.clear();
417 removeNonDecorViews();
418 mCurItem = 0;
419 scrollTo(0, 0);
420 }
421
422 final PagerAdapter oldAdapter = mAdapter;
423 mAdapter = adapter;
424 mExpectedAdapterCount = 0;
425
426 if (mAdapter != null) {
427 if (mObserver == null) {
428 mObserver = new PagerObserver();
429 }
430 mAdapter.registerDataSetObserver(mObserver);
431 mPopulatePending = false;
432 final boolean wasFirstLayout = mFirstLayout;
433 mFirstLayout = true;
434 mExpectedAdapterCount = mAdapter.getCount();
435 if (mRestoredCurItem >= 0) {
436 mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
437 setCurrentItemInternal(mRestoredCurItem, false, true);
438 mRestoredCurItem = -1;
439 mRestoredAdapterState = null;
440 mRestoredClassLoader = null;
441 } else if (!wasFirstLayout) {
442 populate();
443 } else {
444 requestLayout();
445 }
446 }
447
448 if (mAdapterChangeListener != null && oldAdapter != adapter) {
449 mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
450 }
451 }
452
453 private void removeNonDecorViews() {
454 for (int i = 0; i < getChildCount(); i++) {
455 final View child = getChildAt(i);
456 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
457 if (!lp.isDecor) {
458 removeViewAt(i);
459 i--;
460 }
461 }
462 }
463
464 /**
465 * Retrieve the current adapter supplying pages.
466 *
467 * @return The currently registered PagerAdapter
468 */
469 public PagerAdapter getAdapter() {
470 return mAdapter;
471 }
472
473 void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
474 mAdapterChangeListener = listener;
475 }
476
477 private int getClientWidth() {
478 return getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
479 }
480
481 /**
482 * Set the currently selected page. If the ViewPager has already been through its first
483 * layout with its current adapter there will be a smooth animated transition between
484 * the current item and the specified item.
485 *
486 * @param item Item index to select
487 */
488 public void setCurrentItem(int item) {
489 mPopulatePending = false;
490 setCurrentItemInternal(item, !mFirstLayout, false);
491 }
492
493 /**
494 * Set the currently selected page.
495 *
496 * @param item Item index to select
497 * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
498 */
499 public void setCurrentItem(int item, boolean smoothScroll) {
500 mPopulatePending = false;
501 setCurrentItemInternal(item, smoothScroll, false);
502 }
503
504 public int getCurrentItem() {
505 return mCurItem;
506 }
507
508 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
509 setCurrentItemInternal(item, smoothScroll, always, 0);
510 }
511
512 void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
513 if (mAdapter == null || mAdapter.getCount() <= 0) {
514 setScrollingCacheEnabled(false);
515 return;
516 }
517 if (!always && mCurItem == item && mItems.size() != 0) {
518 setScrollingCacheEnabled(false);
519 return;
520 }
521
522 if (item < 0) {
523 item = 0;
524 } else if (item >= mAdapter.getCount()) {
525 item = mAdapter.getCount() - 1;
526 }
527 final int pageLimit = mOffscreenPageLimit;
528 if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
529 // We are doing a jump by more than one page. To avoid
530 // glitches, we want to keep all current pages in the view
531 // until the scroll ends.
532 for (int i=0; i<mItems.size(); i++) {
533 mItems.get(i).scrolling = true;
534 }
535 }
536 final boolean dispatchSelected = mCurItem != item;
537
538 if (mFirstLayout) {
539 // We don't have any idea how big we are yet and shouldn't have any pages either.
540 // Just set things up and let the pending layout handle things.
541 mCurItem = item;
542 if (dispatchSelected && mOnPageChangeListener != null) {
543 mOnPageChangeListener.onPageSelected(item);
544 }
545 if (dispatchSelected && mInternalPageChangeListener != null) {
546 mInternalPageChangeListener.onPageSelected(item);
547 }
548 requestLayout();
549 } else {
550 populate(item);
551 scrollToItem(item, smoothScroll, velocity, dispatchSelected);
552 }
553 }
554
555 private void scrollToItem(int item, boolean smoothScroll, int velocity,
556 boolean dispatchSelected) {
557 final ItemInfo curInfo = infoForPosition(item);
558 int destX = 0;
559 if (curInfo != null) {
560 final int width = getClientWidth();
561 destX = (int) (width * Math.max(mFirstOffset,
562 Math.min(curInfo.offset, mLastOffset)));
563 }
564 if (smoothScroll) {
565 smoothScrollTo(destX, 0, velocity);
566 if (dispatchSelected && mOnPageChangeListener != null) {
567 mOnPageChangeListener.onPageSelected(item);
568 }
569 if (dispatchSelected && mInternalPageChangeListener != null) {
570 mInternalPageChangeListener.onPageSelected(item);
571 }
572 } else {
573 if (dispatchSelected && mOnPageChangeListener != null) {
574 mOnPageChangeListener.onPageSelected(item);
575 }
576 if (dispatchSelected && mInternalPageChangeListener != null) {
577 mInternalPageChangeListener.onPageSelected(item);
578 }
579 completeScroll(false);
580 scrollTo(destX, 0);
581 pageScrolled(destX);
582 }
583 }
584
585 /**
586 * Set a listener that will be invoked whenever the page changes or is incrementally
587 * scrolled. See {@link OnPageChangeListener}.
588 *
589 * @param listener Listener to set
590 */
591 public void setOnPageChangeListener(OnPageChangeListener listener) {
592 mOnPageChangeListener = listener;
593 }
594
595 /**
596 * Set a {@link PageTransformer} that will be called for each attached page whenever
597 * the scroll position is changed. This allows the application to apply custom property
598 * transformations to each page, overriding the default sliding look and feel.
599 *
600 * <p><em>Note:</em> Prior to Android 3.0 the property animation APIs did not exist.
601 * As a result, setting a PageTransformer prior to Android 3.0 (API 11) will have no effect.</p>
602 *
603 * @param reverseDrawingOrder true if the supplied PageTransformer requires page views
604 * to be drawn from last to first instead of first to last.
605 * @param transformer PageTransformer that will modify each page's animation properties
606 */
607 public void setPageTransformer(boolean reverseDrawingOrder, PageTransformer transformer) {
608 final boolean hasTransformer = transformer != null;
609 final boolean needsPopulate = hasTransformer != (mPageTransformer != null);
610 mPageTransformer = transformer;
611 setChildrenDrawingOrderEnabled(hasTransformer);
612 if (hasTransformer) {
613 mDrawingOrder = reverseDrawingOrder ? DRAW_ORDER_REVERSE : DRAW_ORDER_FORWARD;
614 } else {
615 mDrawingOrder = DRAW_ORDER_DEFAULT;
616 }
617 if (needsPopulate) populate();
618 }
619
620 @Override
621 protected int getChildDrawingOrder(int childCount, int i) {
622 final int index = mDrawingOrder == DRAW_ORDER_REVERSE ? childCount - 1 - i : i;
623 final int result = ((LayoutParams) mDrawingOrderedChildren.get(index).getLayoutParams()).childIndex;
624 return result;
625 }
626
627 /**
628 * Set a separate OnPageChangeListener for internal use by the support library.
629 *
630 * @param listener Listener to set
631 * @return The old listener that was set, if any.
632 */
633 OnPageChangeListener setInternalPageChangeListener(OnPageChangeListener listener) {
634 OnPageChangeListener oldListener = mInternalPageChangeListener;
635 mInternalPageChangeListener = listener;
636 return oldListener;
637 }
638
639 /**
640 * Returns the number of pages that will be retained to either side of the
641 * current page in the view hierarchy in an idle state. Defaults to 1.
642 *
643 * @return How many pages will be kept offscreen on either side
644 * @see #setOffscreenPageLimit(int)
645 */
646 public int getOffscreenPageLimit() {
647 return mOffscreenPageLimit;
648 }
649
650 /**
651 * Set the number of pages that should be retained to either side of the
652 * current page in the view hierarchy in an idle state. Pages beyond this
653 * limit will be recreated from the adapter when needed.
654 *
655 * <p>This is offered as an optimization. If you know in advance the number
656 * of pages you will need to support or have lazy-loading mechanisms in place
657 * on your pages, tweaking this setting can have benefits in perceived smoothness
658 * of paging animations and interaction. If you have a small number of pages (3-4)
659 * that you can keep active all at once, less time will be spent in layout for
660 * newly created view subtrees as the user pages back and forth.</p>
661 *
662 * <p>You should keep this limit low, especially if your pages have complex layouts.
663 * This setting defaults to 1.</p>
664 *
665 * @param limit How many pages will be kept offscreen in an idle state.
666 */
667 public void setOffscreenPageLimit(int limit) {
668 if (limit < DEFAULT_OFFSCREEN_PAGES) {
669 Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
670 DEFAULT_OFFSCREEN_PAGES);
671 limit = DEFAULT_OFFSCREEN_PAGES;
672 }
673 if (limit != mOffscreenPageLimit) {
674 mOffscreenPageLimit = limit;
675 populate();
676 }
677 }
678
679 /**
680 * Set the margin between pages.
681 *
682 * @param marginPixels Distance between adjacent pages in pixels
683 * @see #getPageMargin()
684 * @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
685 * @see #setPageMarginDrawable(int)
686 */
687 public void setPageMargin(int marginPixels) {
688 final int oldMargin = mPageMargin;
689 mPageMargin = marginPixels;
690
691 final int width = getWidth();
692 recomputeScrollPosition(width, width, marginPixels, oldMargin);
693
694 requestLayout();
695 }
696
697 /**
698 * Return the margin between pages.
699 *
700 * @return The size of the margin in pixels
701 */
702 public int getPageMargin() {
703 return mPageMargin;
704 }
705
706 /**
707 * Set a drawable that will be used to fill the margin between pages.
708 *
709 * @param d Drawable to display between pages
710 */
711 public void setPageMarginDrawable(Drawable d) {
712 mMarginDrawable = d;
713 if (d != null) refreshDrawableState();
714 setWillNotDraw(d == null);
715 invalidate();
716 }
717
718 /**
719 * Set a drawable that will be used to fill the margin between pages.
720 *
721 * @param resId Resource ID of a drawable to display between pages
722 */
723 public void setPageMarginDrawable(@DrawableRes int resId) {
724 setPageMarginDrawable(getContext().getDrawable(resId));
725 }
726
727 @Override
728 protected boolean verifyDrawable(Drawable who) {
729 return super.verifyDrawable(who) || who == mMarginDrawable;
730 }
731
732 @Override
733 protected void drawableStateChanged() {
734 super.drawableStateChanged();
735 final Drawable d = mMarginDrawable;
736 if (d != null && d.isStateful()) {
737 d.setState(getDrawableState());
738 }
739 }
740
741 // We want the duration of the page snap animation to be influenced by the distance that
742 // the screen has to travel, however, we don't want this duration to be effected in a
743 // purely linear fashion. Instead, we use this method to moderate the effect that the distance
744 // of travel has on the overall snap duration.
745 float distanceInfluenceForSnapDuration(float f) {
746 f -= 0.5f; // center the values about 0.
747 f *= 0.3f * Math.PI / 2.0f;
748 return (float) Math.sin(f);
749 }
750
751 /**
752 * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
753 *
754 * @param x the number of pixels to scroll by on the X axis
755 * @param y the number of pixels to scroll by on the Y axis
756 */
757 void smoothScrollTo(int x, int y) {
758 smoothScrollTo(x, y, 0);
759 }
760
761 /**
762 * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
763 *
764 * @param x the number of pixels to scroll by on the X axis
765 * @param y the number of pixels to scroll by on the Y axis
766 * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
767 */
768 void smoothScrollTo(int x, int y, int velocity) {
769 if (getChildCount() == 0) {
770 // Nothing to do.
771 setScrollingCacheEnabled(false);
772 return;
773 }
774 int sx = getScrollX();
775 int sy = getScrollY();
776 int dx = x - sx;
777 int dy = y - sy;
778 if (dx == 0 && dy == 0) {
779 completeScroll(false);
780 populate();
781 setScrollState(SCROLL_STATE_IDLE);
782 return;
783 }
784
785 setScrollingCacheEnabled(true);
786 setScrollState(SCROLL_STATE_SETTLING);
787
788 final int width = getClientWidth();
789 final int halfWidth = width / 2;
790 final float distanceRatio = Math.min(1f, 1.0f * Math.abs(dx) / width);
791 final float distance = halfWidth + halfWidth *
792 distanceInfluenceForSnapDuration(distanceRatio);
793
794 int duration = 0;
795 velocity = Math.abs(velocity);
796 if (velocity > 0) {
797 duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
798 } else {
799 final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
800 final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
801 duration = (int) ((pageDelta + 1) * 100);
802 }
803 duration = Math.min(duration, MAX_SETTLE_DURATION);
804
805 mScroller.startScroll(sx, sy, dx, dy, duration);
806 postInvalidateOnAnimation();
807 }
808
809 ItemInfo addNewItem(int position, int index) {
810 ItemInfo ii = new ItemInfo();
811 ii.position = position;
812 ii.object = mAdapter.instantiateItem(this, position);
813 ii.widthFactor = mAdapter.getPageWidth(position);
814 if (index < 0 || index >= mItems.size()) {
815 mItems.add(ii);
816 } else {
817 mItems.add(index, ii);
818 }
819 return ii;
820 }
821
822 void dataSetChanged() {
823 // This method only gets called if our observer is attached, so mAdapter is non-null.
824
825 final int adapterCount = mAdapter.getCount();
826 mExpectedAdapterCount = adapterCount;
827 boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&
828 mItems.size() < adapterCount;
829 int newCurrItem = mCurItem;
830
831 boolean isUpdating = false;
832 for (int i = 0; i < mItems.size(); i++) {
833 final ItemInfo ii = mItems.get(i);
834 final int newPos = mAdapter.getItemPosition(ii.object);
835
836 if (newPos == PagerAdapter.POSITION_UNCHANGED) {
837 continue;
838 }
839
840 if (newPos == PagerAdapter.POSITION_NONE) {
841 mItems.remove(i);
842 i--;
843
844 if (!isUpdating) {
845 mAdapter.startUpdate(this);
846 isUpdating = true;
847 }
848
849 mAdapter.destroyItem(this, ii.position, ii.object);
850 needPopulate = true;
851
852 if (mCurItem == ii.position) {
853 // Keep the current item in the valid range
854 newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
855 needPopulate = true;
856 }
857 continue;
858 }
859
860 if (ii.position != newPos) {
861 if (ii.position == mCurItem) {
862 // Our current item changed position. Follow it.
863 newCurrItem = newPos;
864 }
865
866 ii.position = newPos;
867 needPopulate = true;
868 }
869 }
870
871 if (isUpdating) {
872 mAdapter.finishUpdate(this);
873 }
874
875 Collections.sort(mItems, COMPARATOR);
876
877 if (needPopulate) {
878 // Reset our known page widths; populate will recompute them.
879 final int childCount = getChildCount();
880 for (int i = 0; i < childCount; i++) {
881 final View child = getChildAt(i);
882 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
883 if (!lp.isDecor) {
884 lp.widthFactor = 0.f;
885 }
886 }
887
888 setCurrentItemInternal(newCurrItem, false, true);
889 requestLayout();
890 }
891 }
892
893 void populate() {
894 populate(mCurItem);
895 }
896
897 void populate(int newCurrentItem) {
898 ItemInfo oldCurInfo = null;
899 int focusDirection = View.FOCUS_FORWARD;
900 if (mCurItem != newCurrentItem) {
901 focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
902 oldCurInfo = infoForPosition(mCurItem);
903 mCurItem = newCurrentItem;
904 }
905
906 if (mAdapter == null) {
907 sortChildDrawingOrder();
908 return;
909 }
910
911 // Bail now if we are waiting to populate. This is to hold off
912 // on creating views from the time the user releases their finger to
913 // fling to a new position until we have finished the scroll to
914 // that position, avoiding glitches from happening at that point.
915 if (mPopulatePending) {
916 if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
917 sortChildDrawingOrder();
918 return;
919 }
920
921 // Also, don't populate until we are attached to a window. This is to
922 // avoid trying to populate before we have restored our view hierarchy
923 // state and conflicting with what is restored.
924 if (getWindowToken() == null) {
925 return;
926 }
927
928 mAdapter.startUpdate(this);
929
930 final int pageLimit = mOffscreenPageLimit;
931 final int startPos = Math.max(0, mCurItem - pageLimit);
932 final int N = mAdapter.getCount();
933 final int endPos = Math.min(N-1, mCurItem + pageLimit);
934
935 if (N != mExpectedAdapterCount) {
936 String resName;
937 try {
938 resName = getResources().getResourceName(getId());
939 } catch (Resources.NotFoundException e) {
940 resName = Integer.toHexString(getId());
941 }
942 throw new IllegalStateException("The application's PagerAdapter changed the adapter's" +
943 " contents without calling PagerAdapter#notifyDataSetChanged!" +
944 " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N +
945 " Pager id: " + resName +
946 " Pager class: " + getClass() +
947 " Problematic adapter: " + mAdapter.getClass());
948 }
949
950 // Locate the currently focused item or add it if needed.
951 int curIndex = -1;
952 ItemInfo curItem = null;
953 for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
954 final ItemInfo ii = mItems.get(curIndex);
955 if (ii.position >= mCurItem) {
956 if (ii.position == mCurItem) curItem = ii;
957 break;
958 }
959 }
960
961 if (curItem == null && N > 0) {
962 curItem = addNewItem(mCurItem, curIndex);
963 }
964
965 // Fill 3x the available width or up to the number of offscreen
966 // pages requested to either side, whichever is larger.
967 // If we have no current item we have no work to do.
968 if (curItem != null) {
969 float extraWidthLeft = 0.f;
970 int itemIndex = curIndex - 1;
971 ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
972 final int clientWidth = getClientWidth();
973 final float leftWidthNeeded = clientWidth <= 0 ? 0 :
974 2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
975 for (int pos = mCurItem - 1; pos >= 0; pos--) {
976 if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
977 if (ii == null) {
978 break;
979 }
980 if (pos == ii.position && !ii.scrolling) {
981 mItems.remove(itemIndex);
982 mAdapter.destroyItem(this, pos, ii.object);
983 if (DEBUG) {
984 Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
985 " view: " + ((View) ii.object));
986 }
987 itemIndex--;
988 curIndex--;
989 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
990 }
991 } else if (ii != null && pos == ii.position) {
992 extraWidthLeft += ii.widthFactor;
993 itemIndex--;
994 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
995 } else {
996 ii = addNewItem(pos, itemIndex + 1);
997 extraWidthLeft += ii.widthFactor;
998 curIndex++;
999 ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
1000 }
1001 }
1002
1003 float extraWidthRight = curItem.widthFactor;
1004 itemIndex = curIndex + 1;
1005 if (extraWidthRight < 2.f) {
1006 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1007 final float rightWidthNeeded = clientWidth <= 0 ? 0 :
1008 (float) getPaddingRight() / (float) clientWidth + 2.f;
1009 for (int pos = mCurItem + 1; pos < N; pos++) {
1010 if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
1011 if (ii == null) {
1012 break;
1013 }
1014 if (pos == ii.position && !ii.scrolling) {
1015 mItems.remove(itemIndex);
1016 mAdapter.destroyItem(this, pos, ii.object);
1017 if (DEBUG) {
1018 Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
1019 " view: " + ((View) ii.object));
1020 }
1021 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1022 }
1023 } else if (ii != null && pos == ii.position) {
1024 extraWidthRight += ii.widthFactor;
1025 itemIndex++;
1026 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1027 } else {
1028 ii = addNewItem(pos, itemIndex);
1029 itemIndex++;
1030 extraWidthRight += ii.widthFactor;
1031 ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
1032 }
1033 }
1034 }
1035
1036 calculatePageOffsets(curItem, curIndex, oldCurInfo);
1037 }
1038
1039 if (DEBUG) {
1040 Log.i(TAG, "Current page list:");
1041 for (int i=0; i<mItems.size(); i++) {
1042 Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
1043 }
1044 }
1045
1046 mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
1047
1048 mAdapter.finishUpdate(this);
1049
1050 // Check width measurement of current pages and drawing sort order.
1051 // Update LayoutParams as needed.
1052 final int childCount = getChildCount();
1053 for (int i = 0; i < childCount; i++) {
1054 final View child = getChildAt(i);
1055 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1056 lp.childIndex = i;
1057 if (!lp.isDecor && lp.widthFactor == 0.f) {
1058 // 0 means requery the adapter for this, it doesn't have a valid width.
1059 final ItemInfo ii = infoForChild(child);
1060 if (ii != null) {
1061 lp.widthFactor = ii.widthFactor;
1062 lp.position = ii.position;
1063 }
1064 }
1065 }
1066 sortChildDrawingOrder();
1067
1068 if (hasFocus()) {
1069 View currentFocused = findFocus();
1070 ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
1071 if (ii == null || ii.position != mCurItem) {
1072 for (int i=0; i<getChildCount(); i++) {
1073 View child = getChildAt(i);
1074 ii = infoForChild(child);
1075 if (ii != null && ii.position == mCurItem) {
1076 if (child.requestFocus(focusDirection)) {
1077 break;
1078 }
1079 }
1080 }
1081 }
1082 }
1083 }
1084
1085 private void sortChildDrawingOrder() {
1086 if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
1087 if (mDrawingOrderedChildren == null) {
1088 mDrawingOrderedChildren = new ArrayList<View>();
1089 } else {
1090 mDrawingOrderedChildren.clear();
1091 }
1092 final int childCount = getChildCount();
1093 for (int i = 0; i < childCount; i++) {
1094 final View child = getChildAt(i);
1095 mDrawingOrderedChildren.add(child);
1096 }
1097 Collections.sort(mDrawingOrderedChildren, sPositionComparator);
1098 }
1099 }
1100
1101 private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
1102 final int N = mAdapter.getCount();
1103 final int width = getClientWidth();
1104 final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
1105 // Fix up offsets for later layout.
1106 if (oldCurInfo != null) {
1107 final int oldCurPosition = oldCurInfo.position;
1108 // Base offsets off of oldCurInfo.
1109 if (oldCurPosition < curItem.position) {
1110 int itemIndex = 0;
1111 ItemInfo ii = null;
1112 float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
1113 for (int pos = oldCurPosition + 1;
1114 pos <= curItem.position && itemIndex < mItems.size(); pos++) {
1115 ii = mItems.get(itemIndex);
1116 while (pos > ii.position && itemIndex < mItems.size() - 1) {
1117 itemIndex++;
1118 ii = mItems.get(itemIndex);
1119 }
1120 while (pos < ii.position) {
1121 // We don't have an item populated for this,
1122 // ask the adapter for an offset.
1123 offset += mAdapter.getPageWidth(pos) + marginOffset;
1124 pos++;
1125 }
1126 ii.offset = offset;
1127 offset += ii.widthFactor + marginOffset;
1128 }
1129 } else if (oldCurPosition > curItem.position) {
1130 int itemIndex = mItems.size() - 1;
1131 ItemInfo ii = null;
1132 float offset = oldCurInfo.offset;
1133 for (int pos = oldCurPosition - 1;
1134 pos >= curItem.position && itemIndex >= 0; pos--) {
1135 ii = mItems.get(itemIndex);
1136 while (pos < ii.position && itemIndex > 0) {
1137 itemIndex--;
1138 ii = mItems.get(itemIndex);
1139 }
1140 while (pos > ii.position) {
1141 // We don't have an item populated for this,
1142 // ask the adapter for an offset.
1143 offset -= mAdapter.getPageWidth(pos) + marginOffset;
1144 pos--;
1145 }
1146 offset -= ii.widthFactor + marginOffset;
1147 ii.offset = offset;
1148 }
1149 }
1150 }
1151
1152 // Base all offsets off of curItem.
1153 final int itemCount = mItems.size();
1154 float offset = curItem.offset;
1155 int pos = curItem.position - 1;
1156 mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
1157 mLastOffset = curItem.position == N - 1 ?
1158 curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
1159 // Previous pages
1160 for (int i = curIndex - 1; i >= 0; i--, pos--) {
1161 final ItemInfo ii = mItems.get(i);
1162 while (pos > ii.position) {
1163 offset -= mAdapter.getPageWidth(pos--) + marginOffset;
1164 }
1165 offset -= ii.widthFactor + marginOffset;
1166 ii.offset = offset;
1167 if (ii.position == 0) mFirstOffset = offset;
1168 }
1169 offset = curItem.offset + curItem.widthFactor + marginOffset;
1170 pos = curItem.position + 1;
1171 // Next pages
1172 for (int i = curIndex + 1; i < itemCount; i++, pos++) {
1173 final ItemInfo ii = mItems.get(i);
1174 while (pos < ii.position) {
1175 offset += mAdapter.getPageWidth(pos++) + marginOffset;
1176 }
1177 if (ii.position == N - 1) {
1178 mLastOffset = offset + ii.widthFactor - 1;
1179 }
1180 ii.offset = offset;
1181 offset += ii.widthFactor + marginOffset;
1182 }
1183
1184 mNeedCalculatePageOffsets = false;
1185 }
1186
1187 /**
1188 * This is the persistent state that is saved by ViewPager. Only needed
1189 * if you are creating a sublass of ViewPager that must save its own
1190 * state, in which case it should implement a subclass of this which
1191 * contains that state.
1192 */
1193 public static class SavedState extends BaseSavedState {
1194 int position;
1195 Parcelable adapterState;
1196 ClassLoader loader;
1197
1198 public SavedState(Parcel source) {
1199 super(source);
1200 }
1201
1202 public SavedState(Parcelable superState) {
1203 super(superState);
1204 }
1205
1206 @Override
1207 public void writeToParcel(Parcel out, int flags) {
1208 super.writeToParcel(out, flags);
1209 out.writeInt(position);
1210 out.writeParcelable(adapterState, flags);
1211 }
1212
1213 @Override
1214 public String toString() {
1215 return "FragmentPager.SavedState{"
1216 + Integer.toHexString(System.identityHashCode(this))
1217 + " position=" + position + "}";
1218 }
1219
1220 public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
1221 @Override
1222 public SavedState createFromParcel(Parcel in) {
1223 return new SavedState(in);
1224 }
1225 @Override
1226 public SavedState[] newArray(int size) {
1227 return new SavedState[size];
1228 }
1229 };
1230
1231 SavedState(Parcel in, ClassLoader loader) {
1232 super(in);
1233 if (loader == null) {
1234 loader = getClass().getClassLoader();
1235 }
1236 position = in.readInt();
1237 adapterState = in.readParcelable(loader);
1238 this.loader = loader;
1239 }
1240 }
1241
1242 @Override
1243 public Parcelable onSaveInstanceState() {
1244 Parcelable superState = super.onSaveInstanceState();
1245 SavedState ss = new SavedState(superState);
1246 ss.position = mCurItem;
1247 if (mAdapter != null) {
1248 ss.adapterState = mAdapter.saveState();
1249 }
1250 return ss;
1251 }
1252
1253 @Override
1254 public void onRestoreInstanceState(Parcelable state) {
1255 if (!(state instanceof SavedState)) {
1256 super.onRestoreInstanceState(state);
1257 return;
1258 }
1259
1260 SavedState ss = (SavedState)state;
1261 super.onRestoreInstanceState(ss.getSuperState());
1262
1263 if (mAdapter != null) {
1264 mAdapter.restoreState(ss.adapterState, ss.loader);
1265 setCurrentItemInternal(ss.position, false, true);
1266 } else {
1267 mRestoredCurItem = ss.position;
1268 mRestoredAdapterState = ss.adapterState;
1269 mRestoredClassLoader = ss.loader;
1270 }
1271 }
1272
1273 @Override
1274 public void addView(View child, int index, ViewGroup.LayoutParams params) {
1275 if (!checkLayoutParams(params)) {
1276 params = generateLayoutParams(params);
1277 }
1278 final LayoutParams lp = (LayoutParams) params;
1279 lp.isDecor |= child instanceof Decor;
1280 if (mInLayout) {
1281 if (lp != null && lp.isDecor) {
1282 throw new IllegalStateException("Cannot add pager decor view during layout");
1283 }
1284 lp.needsMeasure = true;
1285 addViewInLayout(child, index, params);
1286 } else {
1287 super.addView(child, index, params);
1288 }
1289
1290 if (USE_CACHE) {
1291 if (child.getVisibility() != GONE) {
1292 child.setDrawingCacheEnabled(mScrollingCacheEnabled);
1293 } else {
1294 child.setDrawingCacheEnabled(false);
1295 }
1296 }
1297 }
1298
1299 @Override
1300 public void removeView(View view) {
1301 if (mInLayout) {
1302 removeViewInLayout(view);
1303 } else {
1304 super.removeView(view);
1305 }
1306 }
1307
1308 ItemInfo infoForChild(View child) {
1309 for (int i=0; i<mItems.size(); i++) {
1310 ItemInfo ii = mItems.get(i);
1311 if (mAdapter.isViewFromObject(child, ii.object)) {
1312 return ii;
1313 }
1314 }
1315 return null;
1316 }
1317
1318 ItemInfo infoForAnyChild(View child) {
1319 ViewParent parent;
1320 while ((parent=child.getParent()) != this) {
1321 if (parent == null || !(parent instanceof View)) {
1322 return null;
1323 }
1324 child = (View)parent;
1325 }
1326 return infoForChild(child);
1327 }
1328
1329 ItemInfo infoForPosition(int position) {
1330 for (int i = 0; i < mItems.size(); i++) {
1331 ItemInfo ii = mItems.get(i);
1332 if (ii.position == position) {
1333 return ii;
1334 }
1335 }
1336 return null;
1337 }
1338
1339 @Override
1340 protected void onAttachedToWindow() {
1341 super.onAttachedToWindow();
1342 mFirstLayout = true;
1343 }
1344
1345 @Override
1346 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1347 // For simple implementation, our internal size is always 0.
1348 // We depend on the container to specify the layout size of
1349 // our view. We can't really know what it is since we will be
1350 // adding and removing different arbitrary views and do not
1351 // want the layout to change as this happens.
1352 setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
1353 getDefaultSize(0, heightMeasureSpec));
1354
1355 final int measuredWidth = getMeasuredWidth();
1356 final int maxGutterSize = measuredWidth / 10;
1357 mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);
1358
1359 // Children are just made to fill our space.
1360 int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
1361 int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
1362
1363 /*
1364 * Make sure all children have been properly measured. Decor views first.
1365 * Right now we cheat and make this less complicated by assuming decor
1366 * views won't intersect. We will pin to edges based on gravity.
1367 */
1368 int size = getChildCount();
1369 for (int i = 0; i < size; ++i) {
1370 final View child = getChildAt(i);
1371 if (child.getVisibility() != GONE) {
1372 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1373 if (lp != null && lp.isDecor) {
1374 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1375 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1376 int widthMode = MeasureSpec.AT_MOST;
1377 int heightMode = MeasureSpec.AT_MOST;
1378 boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
1379 boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;
1380
1381 if (consumeVertical) {
1382 widthMode = MeasureSpec.EXACTLY;
1383 } else if (consumeHorizontal) {
1384 heightMode = MeasureSpec.EXACTLY;
1385 }
1386
1387 int widthSize = childWidthSize;
1388 int heightSize = childHeightSize;
1389 if (lp.width != LayoutParams.WRAP_CONTENT) {
1390 widthMode = MeasureSpec.EXACTLY;
1391 if (lp.width != LayoutParams.FILL_PARENT) {
1392 widthSize = lp.width;
1393 }
1394 }
1395 if (lp.height != LayoutParams.WRAP_CONTENT) {
1396 heightMode = MeasureSpec.EXACTLY;
1397 if (lp.height != LayoutParams.FILL_PARENT) {
1398 heightSize = lp.height;
1399 }
1400 }
1401 final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
1402 final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
1403 child.measure(widthSpec, heightSpec);
1404
1405 if (consumeVertical) {
1406 childHeightSize -= child.getMeasuredHeight();
1407 } else if (consumeHorizontal) {
1408 childWidthSize -= child.getMeasuredWidth();
1409 }
1410 }
1411 }
1412 }
1413
1414 mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
1415 mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);
1416
1417 // Make sure we have created all fragments that we need to have shown.
1418 mInLayout = true;
1419 populate();
1420 mInLayout = false;
1421
1422 // Page views next.
1423 size = getChildCount();
1424 for (int i = 0; i < size; ++i) {
1425 final View child = getChildAt(i);
1426 if (child.getVisibility() != GONE) {
1427 if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
1428 + ": " + mChildWidthMeasureSpec);
1429
1430 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1431 if (lp == null || !lp.isDecor) {
1432 final int widthSpec = MeasureSpec.makeMeasureSpec(
1433 (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
1434 child.measure(widthSpec, mChildHeightMeasureSpec);
1435 }
1436 }
1437 }
1438 }
1439
1440 @Override
1441 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
1442 super.onSizeChanged(w, h, oldw, oldh);
1443
1444 // Make sure scroll position is set correctly.
1445 if (w != oldw) {
1446 recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
1447 }
1448 }
1449
1450 private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
1451 if (oldWidth > 0 && !mItems.isEmpty()) {
1452 final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin;
1453 final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight()
1454 + oldMargin;
1455 final int xpos = getScrollX();
1456 final float pageOffset = (float) xpos / oldWidthWithMargin;
1457 final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
1458
1459 scrollTo(newOffsetPixels, getScrollY());
1460 if (!mScroller.isFinished()) {
1461 // We now return to your regularly scheduled scroll, already in progress.
1462 final int newDuration = mScroller.getDuration() - mScroller.timePassed();
1463 ItemInfo targetInfo = infoForPosition(mCurItem);
1464 mScroller.startScroll(newOffsetPixels, 0,
1465 (int) (targetInfo.offset * width), 0, newDuration);
1466 }
1467 } else {
1468 final ItemInfo ii = infoForPosition(mCurItem);
1469 final float scrollOffset = ii != null ? Math.min(ii.offset, mLastOffset) : 0;
1470 final int scrollPos = (int) (scrollOffset *
1471 (width - getPaddingLeft() - getPaddingRight()));
1472 if (scrollPos != getScrollX()) {
1473 completeScroll(false);
1474 scrollTo(scrollPos, getScrollY());
1475 }
1476 }
1477 }
1478
1479 @Override
1480 protected void onLayout(boolean changed, int l, int t, int r, int b) {
1481 final int count = getChildCount();
1482 int width = r - l;
1483 int height = b - t;
1484 int paddingLeft = getPaddingLeft();
1485 int paddingTop = getPaddingTop();
1486 int paddingRight = getPaddingRight();
1487 int paddingBottom = getPaddingBottom();
1488 final int scrollX = getScrollX();
1489
1490 int decorCount = 0;
1491
1492 // First pass - decor views. We need to do this in two passes so that
1493 // we have the proper offsets for non-decor views later.
1494 for (int i = 0; i < count; i++) {
1495 final View child = getChildAt(i);
1496 if (child.getVisibility() != GONE) {
1497 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1498 int childLeft = 0;
1499 int childTop = 0;
1500 if (lp.isDecor) {
1501 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1502 final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
1503 switch (hgrav) {
1504 default:
1505 childLeft = paddingLeft;
1506 break;
1507 case Gravity.LEFT:
1508 childLeft = paddingLeft;
1509 paddingLeft += child.getMeasuredWidth();
1510 break;
1511 case Gravity.CENTER_HORIZONTAL:
1512 childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1513 paddingLeft);
1514 break;
1515 case Gravity.RIGHT:
1516 childLeft = width - paddingRight - child.getMeasuredWidth();
1517 paddingRight += child.getMeasuredWidth();
1518 break;
1519 }
1520 switch (vgrav) {
1521 default:
1522 childTop = paddingTop;
1523 break;
1524 case Gravity.TOP:
1525 childTop = paddingTop;
1526 paddingTop += child.getMeasuredHeight();
1527 break;
1528 case Gravity.CENTER_VERTICAL:
1529 childTop = Math.max((height - child.getMeasuredHeight()) / 2,
1530 paddingTop);
1531 break;
1532 case Gravity.BOTTOM:
1533 childTop = height - paddingBottom - child.getMeasuredHeight();
1534 paddingBottom += child.getMeasuredHeight();
1535 break;
1536 }
1537 childLeft += scrollX;
1538 child.layout(childLeft, childTop,
1539 childLeft + child.getMeasuredWidth(),
1540 childTop + child.getMeasuredHeight());
1541 decorCount++;
1542 }
1543 }
1544 }
1545
1546 final int childWidth = width - paddingLeft - paddingRight;
1547 // Page views. Do this once we have the right padding offsets from above.
1548 for (int i = 0; i < count; i++) {
1549 final View child = getChildAt(i);
1550 if (child.getVisibility() != GONE) {
1551 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1552 ItemInfo ii;
1553 if (!lp.isDecor && (ii = infoForChild(child)) != null) {
1554 int loff = (int) (childWidth * ii.offset);
1555 int childLeft = paddingLeft + loff;
1556 int childTop = paddingTop;
1557 if (lp.needsMeasure) {
1558 // This was added during layout and needs measurement.
1559 // Do it now that we know what we're working with.
1560 lp.needsMeasure = false;
1561 final int widthSpec = MeasureSpec.makeMeasureSpec(
1562 (int) (childWidth * lp.widthFactor),
1563 MeasureSpec.EXACTLY);
1564 final int heightSpec = MeasureSpec.makeMeasureSpec(
1565 (int) (height - paddingTop - paddingBottom),
1566 MeasureSpec.EXACTLY);
1567 child.measure(widthSpec, heightSpec);
1568 }
1569 if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
1570 + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
1571 + "x" + child.getMeasuredHeight());
1572 child.layout(childLeft, childTop,
1573 childLeft + child.getMeasuredWidth(),
1574 childTop + child.getMeasuredHeight());
1575 }
1576 }
1577 }
1578 mTopPageBounds = paddingTop;
1579 mBottomPageBounds = height - paddingBottom;
1580 mDecorChildCount = decorCount;
1581
1582 if (mFirstLayout) {
1583 scrollToItem(mCurItem, false, 0, false);
1584 }
1585 mFirstLayout = false;
1586 }
1587
1588 @Override
1589 public void computeScroll() {
1590 if (!mScroller.isFinished() && mScroller.computeScrollOffset()) {
1591 int oldX = getScrollX();
1592 int oldY = getScrollY();
1593 int x = mScroller.getCurrX();
1594 int y = mScroller.getCurrY();
1595
1596 if (oldX != x || oldY != y) {
1597 scrollTo(x, y);
1598 if (!pageScrolled(x)) {
1599 mScroller.abortAnimation();
1600 scrollTo(0, y);
1601 }
1602 }
1603
1604 // Keep on drawing until the animation has finished.
1605 postInvalidateOnAnimation();
1606 return;
1607 }
1608
1609 // Done with scroll, clean up state.
1610 completeScroll(true);
1611 }
1612
1613 private boolean pageScrolled(int xpos) {
1614 if (mItems.size() == 0) {
1615 mCalledSuper = false;
1616 onPageScrolled(0, 0, 0);
1617 if (!mCalledSuper) {
1618 throw new IllegalStateException(
1619 "onPageScrolled did not call superclass implementation");
1620 }
1621 return false;
1622 }
1623 final ItemInfo ii = infoForCurrentScrollPosition();
1624 final int width = getClientWidth();
1625 final int widthWithMargin = width + mPageMargin;
1626 final float marginOffset = (float) mPageMargin / width;
1627 final int currentPage = ii.position;
1628 final float pageOffset = (((float) xpos / width) - ii.offset) /
1629 (ii.widthFactor + marginOffset);
1630 final int offsetPixels = (int) (pageOffset * widthWithMargin);
1631
1632 mCalledSuper = false;
1633 onPageScrolled(currentPage, pageOffset, offsetPixels);
1634 if (!mCalledSuper) {
1635 throw new IllegalStateException(
1636 "onPageScrolled did not call superclass implementation");
1637 }
1638 return true;
1639 }
1640
1641 /**
1642 * This method will be invoked when the current page is scrolled, either as part
1643 * of a programmatically initiated smooth scroll or a user initiated touch scroll.
1644 * If you override this method you must call through to the superclass implementation
1645 * (e.g. super.onPageScrolled(position, offset, offsetPixels)) before onPageScrolled
1646 * returns.
1647 *
1648 * @param position Position index of the first page currently being displayed.
1649 * Page position+1 will be visible if positionOffset is nonzero.
1650 * @param offset Value from [0, 1) indicating the offset from the page at position.
1651 * @param offsetPixels Value in pixels indicating the offset from position.
1652 */
1653 protected void onPageScrolled(int position, float offset, int offsetPixels) {
1654 // Offset any decor views if needed - keep them on-screen at all times.
1655 if (mDecorChildCount > 0) {
1656 final int scrollX = getScrollX();
1657 int paddingLeft = getPaddingLeft();
1658 int paddingRight = getPaddingRight();
1659 final int width = getWidth();
1660 final int childCount = getChildCount();
1661 for (int i = 0; i < childCount; i++) {
1662 final View child = getChildAt(i);
1663 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1664 if (!lp.isDecor) continue;
1665
1666 final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
1667 int childLeft = 0;
1668 switch (hgrav) {
1669 default:
1670 childLeft = paddingLeft;
1671 break;
1672 case Gravity.LEFT:
1673 childLeft = paddingLeft;
1674 paddingLeft += child.getWidth();
1675 break;
1676 case Gravity.CENTER_HORIZONTAL:
1677 childLeft = Math.max((width - child.getMeasuredWidth()) / 2,
1678 paddingLeft);
1679 break;
1680 case Gravity.RIGHT:
1681 childLeft = width - paddingRight - child.getMeasuredWidth();
1682 paddingRight += child.getMeasuredWidth();
1683 break;
1684 }
1685 childLeft += scrollX;
1686
1687 final int childOffset = childLeft - child.getLeft();
1688 if (childOffset != 0) {
1689 child.offsetLeftAndRight(childOffset);
1690 }
1691 }
1692 }
1693
1694 if (mOnPageChangeListener != null) {
1695 mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1696 }
1697 if (mInternalPageChangeListener != null) {
1698 mInternalPageChangeListener.onPageScrolled(position, offset, offsetPixels);
1699 }
1700
1701 if (mPageTransformer != null) {
1702 final int scrollX = getScrollX();
1703 final int childCount = getChildCount();
1704 for (int i = 0; i < childCount; i++) {
1705 final View child = getChildAt(i);
1706 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
1707
1708 if (lp.isDecor) continue;
1709
1710 final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
1711 mPageTransformer.transformPage(child, transformPos);
1712 }
1713 }
1714
1715 mCalledSuper = true;
1716 }
1717
1718 private void completeScroll(boolean postEvents) {
1719 boolean needPopulate = mScrollState == SCROLL_STATE_SETTLING;
1720 if (needPopulate) {
1721 // Done with scroll, no longer want to cache view drawing.
1722 setScrollingCacheEnabled(false);
1723 mScroller.abortAnimation();
1724 int oldX = getScrollX();
1725 int oldY = getScrollY();
1726 int x = mScroller.getCurrX();
1727 int y = mScroller.getCurrY();
1728 if (oldX != x || oldY != y) {
1729 scrollTo(x, y);
1730 }
1731 }
1732 mPopulatePending = false;
1733 for (int i=0; i<mItems.size(); i++) {
1734 ItemInfo ii = mItems.get(i);
1735 if (ii.scrolling) {
1736 needPopulate = true;
1737 ii.scrolling = false;
1738 }
1739 }
1740 if (needPopulate) {
1741 if (postEvents) {
1742 postOnAnimation(mEndScrollRunnable);
1743 } else {
1744 mEndScrollRunnable.run();
1745 }
1746 }
1747 }
1748
1749 private boolean isGutterDrag(float x, float dx) {
1750 return (x < mGutterSize && dx > 0) || (x > getWidth() - mGutterSize && dx < 0);
1751 }
1752
1753 private void enableLayers(boolean enable) {
1754 final int childCount = getChildCount();
1755 for (int i = 0; i < childCount; i++) {
1756 final int layerType = enable ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE;
1757 getChildAt(i).setLayerType(layerType, null);
1758 }
1759 }
1760
1761 @Override
1762 public boolean onInterceptTouchEvent(MotionEvent ev) {
1763 /*
1764 * This method JUST determines whether we want to intercept the motion.
1765 * If we return true, onMotionEvent will be called and we do the actual
1766 * scrolling there.
1767 */
1768
1769 final int action = ev.getAction() & MotionEvent.ACTION_MASK;
1770
1771 // Always take care of the touch gesture being complete.
1772 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
1773 // Release the drag.
1774 if (DEBUG) Log.v(TAG, "Intercept done!");
1775 mIsBeingDragged = false;
1776 mIsUnableToDrag = false;
1777 mActivePointerId = INVALID_POINTER;
1778 if (mVelocityTracker != null) {
1779 mVelocityTracker.recycle();
1780 mVelocityTracker = null;
1781 }
1782 return false;
1783 }
1784
1785 // Nothing more to do here if we have decided whether or not we
1786 // are dragging.
1787 if (action != MotionEvent.ACTION_DOWN) {
1788 if (mIsBeingDragged) {
1789 if (DEBUG) Log.v(TAG, "Intercept returning true!");
1790 return true;
1791 }
1792 if (mIsUnableToDrag) {
1793 if (DEBUG) Log.v(TAG, "Intercept returning false!");
1794 return false;
1795 }
1796 }
1797
1798 switch (action) {
1799 case MotionEvent.ACTION_MOVE: {
1800 /*
1801 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1802 * whether the user has moved far enough from his original down touch.
1803 */
1804
1805 /*
1806 * Locally do absolute value. mLastMotionY is set to the y value
1807 * of the down event.
1808 */
1809 final int activePointerId = mActivePointerId;
1810 if (activePointerId == INVALID_POINTER) {
1811 // If we don't have a valid id, the touch down wasn't on content.
1812 break;
1813 }
1814
1815 final int pointerIndex = ev.findPointerIndex(activePointerId);
1816 final float x = ev.getX(pointerIndex);
1817 final float dx = x - mLastMotionX;
1818 final float xDiff = Math.abs(dx);
1819 final float y = ev.getY(pointerIndex);
1820 final float yDiff = Math.abs(y - mInitialMotionY);
1821 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1822
1823 if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
1824 canScroll(this, false, (int) dx, (int) x, (int) y)) {
1825 // Nested view has scrollable area under this point. Let it be handled there.
1826 mLastMotionX = x;
1827 mLastMotionY = y;
1828 mIsUnableToDrag = true;
1829 return false;
1830 }
1831 if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
1832 if (DEBUG) Log.v(TAG, "Starting drag!");
1833 mIsBeingDragged = true;
1834 requestParentDisallowInterceptTouchEvent(true);
1835 setScrollState(SCROLL_STATE_DRAGGING);
1836 mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
1837 mInitialMotionX - mTouchSlop;
1838 mLastMotionY = y;
1839 setScrollingCacheEnabled(true);
1840 } else if (yDiff > mTouchSlop) {
1841 // The finger has moved enough in the vertical
1842 // direction to be counted as a drag... abort
1843 // any attempt to drag horizontally, to work correctly
1844 // with children that have scrolling containers.
1845 if (DEBUG) Log.v(TAG, "Starting unable to drag!");
1846 mIsUnableToDrag = true;
1847 }
1848 if (mIsBeingDragged) {
1849 // Scroll to follow the motion event
1850 if (performDrag(x)) {
1851 postInvalidateOnAnimation();
1852 }
1853 }
1854 break;
1855 }
1856
1857 case MotionEvent.ACTION_DOWN: {
1858 /*
1859 * Remember location of down touch.
1860 * ACTION_DOWN always refers to pointer index 0.
1861 */
1862 mLastMotionX = mInitialMotionX = ev.getX();
1863 mLastMotionY = mInitialMotionY = ev.getY();
1864 mActivePointerId = ev.getPointerId(0);
1865 mIsUnableToDrag = false;
1866
1867 mScroller.computeScrollOffset();
1868 if (mScrollState == SCROLL_STATE_SETTLING &&
1869 Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
1870 // Let the user 'catch' the pager as it animates.
1871 mScroller.abortAnimation();
1872 mPopulatePending = false;
1873 populate();
1874 mIsBeingDragged = true;
1875 requestParentDisallowInterceptTouchEvent(true);
1876 setScrollState(SCROLL_STATE_DRAGGING);
1877 } else {
1878 completeScroll(false);
1879 mIsBeingDragged = false;
1880 }
1881
1882 if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
1883 + " mIsBeingDragged=" + mIsBeingDragged
1884 + "mIsUnableToDrag=" + mIsUnableToDrag);
1885 break;
1886 }
1887
1888 case MotionEvent.ACTION_POINTER_UP:
1889 onSecondaryPointerUp(ev);
1890 break;
1891 }
1892
1893 if (mVelocityTracker == null) {
1894 mVelocityTracker = VelocityTracker.obtain();
1895 }
1896 mVelocityTracker.addMovement(ev);
1897
1898 /*
1899 * The only time we want to intercept motion events is if we are in the
1900 * drag mode.
1901 */
1902 return mIsBeingDragged;
1903 }
1904
1905 @Override
1906 public boolean onTouchEvent(MotionEvent ev) {
1907 if (mFakeDragging) {
1908 // A fake drag is in progress already, ignore this real one
1909 // but still eat the touch events.
1910 // (It is likely that the user is multi-touching the screen.)
1911 return true;
1912 }
1913
1914 if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
1915 // Don't handle edge touches immediately -- they may actually belong to one of our
1916 // descendants.
1917 return false;
1918 }
1919
1920 if (mAdapter == null || mAdapter.getCount() == 0) {
1921 // Nothing to present or scroll; nothing to touch.
1922 return false;
1923 }
1924
1925 if (mVelocityTracker == null) {
1926 mVelocityTracker = VelocityTracker.obtain();
1927 }
1928 mVelocityTracker.addMovement(ev);
1929
1930 final int action = ev.getAction();
1931 boolean needsInvalidate = false;
1932
1933 switch (action & MotionEvent.ACTION_MASK) {
1934 case MotionEvent.ACTION_DOWN: {
1935 mScroller.abortAnimation();
1936 mPopulatePending = false;
1937 populate();
1938
1939 // Remember where the motion event started
1940 mLastMotionX = mInitialMotionX = ev.getX();
1941 mLastMotionY = mInitialMotionY = ev.getY();
1942 mActivePointerId = ev.getPointerId(0);
1943 break;
1944 }
1945 case MotionEvent.ACTION_MOVE:
1946 if (!mIsBeingDragged) {
1947 final int pointerIndex = ev.findPointerIndex(mActivePointerId);
1948 final float x = ev.getX(pointerIndex);
1949 final float xDiff = Math.abs(x - mLastMotionX);
1950 final float y = ev.getY(pointerIndex);
1951 final float yDiff = Math.abs(y - mLastMotionY);
1952 if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
1953 if (xDiff > mTouchSlop && xDiff > yDiff) {
1954 if (DEBUG) Log.v(TAG, "Starting drag!");
1955 mIsBeingDragged = true;
1956 requestParentDisallowInterceptTouchEvent(true);
1957 mLastMotionX = x - mInitialMotionX > 0 ? mInitialMotionX + mTouchSlop :
1958 mInitialMotionX - mTouchSlop;
1959 mLastMotionY = y;
1960 setScrollState(SCROLL_STATE_DRAGGING);
1961 setScrollingCacheEnabled(true);
1962
1963 // Disallow Parent Intercept, just in case
1964 ViewParent parent = getParent();
1965 if (parent != null) {
1966 parent.requestDisallowInterceptTouchEvent(true);
1967 }
1968 }
1969 }
1970 // Not else! Note that mIsBeingDragged can be set above.
1971 if (mIsBeingDragged) {
1972 // Scroll to follow the motion event
1973 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1974 final float x = ev.getX(activePointerIndex);
1975 needsInvalidate |= performDrag(x);
1976 }
1977 break;
1978 case MotionEvent.ACTION_UP:
1979 if (mIsBeingDragged) {
1980 final VelocityTracker velocityTracker = mVelocityTracker;
1981 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1982 int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
1983 mPopulatePending = true;
1984 final int width = getClientWidth();
1985 final int scrollX = getScrollX();
1986 final ItemInfo ii = infoForCurrentScrollPosition();
1987 final int currentPage = ii.position;
1988 final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
1989 final int activePointerIndex =
1990 ev.findPointerIndex(mActivePointerId);
1991 final float x = ev.getX(activePointerIndex);
1992 final int totalDelta = (int) (x - mInitialMotionX);
1993 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
1994 totalDelta);
1995 setCurrentItemInternal(nextPage, true, true, initialVelocity);
1996
1997 mActivePointerId = INVALID_POINTER;
1998 endDrag();
1999 mLeftEdge.onRelease();
2000 mRightEdge.onRelease();
2001 needsInvalidate = true;
2002 }
2003 break;
2004 case MotionEvent.ACTION_CANCEL:
2005 if (mIsBeingDragged) {
2006 scrollToItem(mCurItem, true, 0, false);
2007 mActivePointerId = INVALID_POINTER;
2008 endDrag();
2009 mLeftEdge.onRelease();
2010 mRightEdge.onRelease();
2011 needsInvalidate = true;
2012 }
2013 break;
2014 case MotionEvent.ACTION_POINTER_DOWN: {
2015 final int index = ev.getActionIndex();
2016 final float x = ev.getX(index);
2017 mLastMotionX = x;
2018 mActivePointerId = ev.getPointerId(index);
2019 break;
2020 }
2021 case MotionEvent.ACTION_POINTER_UP:
2022 onSecondaryPointerUp(ev);
2023 mLastMotionX = ev.getX(ev.findPointerIndex(mActivePointerId));
2024 break;
2025 }
2026 if (needsInvalidate) {
2027 postInvalidateOnAnimation();
2028 }
2029 return true;
2030 }
2031
2032 private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
2033 final ViewParent parent = getParent();
2034 if (parent != null) {
2035 parent.requestDisallowInterceptTouchEvent(disallowIntercept);
2036 }
2037 }
2038
2039 private boolean performDrag(float x) {
2040 boolean needsInvalidate = false;
2041
2042 final float deltaX = mLastMotionX - x;
2043 mLastMotionX = x;
2044
2045 float oldScrollX = getScrollX();
2046 float scrollX = oldScrollX + deltaX;
2047 final int width = getClientWidth();
2048
2049 float leftBound = width * mFirstOffset;
2050 float rightBound = width * mLastOffset;
2051 boolean leftAbsolute = true;
2052 boolean rightAbsolute = true;
2053
2054 final ItemInfo firstItem = mItems.get(0);
2055 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2056 if (firstItem.position != 0) {
2057 leftAbsolute = false;
2058 leftBound = firstItem.offset * width;
2059 }
2060 if (lastItem.position != mAdapter.getCount() - 1) {
2061 rightAbsolute = false;
2062 rightBound = lastItem.offset * width;
2063 }
2064
2065 if (scrollX < leftBound) {
2066 if (leftAbsolute) {
2067 float over = leftBound - scrollX;
2068 mLeftEdge.onPull(Math.abs(over) / width);
2069 needsInvalidate = true;
2070 }
2071 scrollX = leftBound;
2072 } else if (scrollX > rightBound) {
2073 if (rightAbsolute) {
2074 float over = scrollX - rightBound;
2075 mRightEdge.onPull(Math.abs(over) / width);
2076 needsInvalidate = true;
2077 }
2078 scrollX = rightBound;
2079 }
2080 // Don't lose the rounded component
2081 mLastMotionX += scrollX - (int) scrollX;
2082 scrollTo((int) scrollX, getScrollY());
2083 pageScrolled((int) scrollX);
2084
2085 return needsInvalidate;
2086 }
2087
2088 /**
2089 * @return Info about the page at the current scroll position.
2090 * This can be synthetic for a missing middle page; the 'object' field can be null.
2091 */
2092 private ItemInfo infoForCurrentScrollPosition() {
2093 final int width = getClientWidth();
2094 final float scrollOffset = width > 0 ? (float) getScrollX() / width : 0;
2095 final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
2096 int lastPos = -1;
2097 float lastOffset = 0.f;
2098 float lastWidth = 0.f;
2099 boolean first = true;
2100
2101 ItemInfo lastItem = null;
2102 for (int i = 0; i < mItems.size(); i++) {
2103 ItemInfo ii = mItems.get(i);
2104 float offset;
2105 if (!first && ii.position != lastPos + 1) {
2106 // Create a synthetic item for a missing page.
2107 ii = mTempItem;
2108 ii.offset = lastOffset + lastWidth + marginOffset;
2109 ii.position = lastPos + 1;
2110 ii.widthFactor = mAdapter.getPageWidth(ii.position);
2111 i--;
2112 }
2113 offset = ii.offset;
2114
2115 final float leftBound = offset;
2116 final float rightBound = offset + ii.widthFactor + marginOffset;
2117 if (first || scrollOffset >= leftBound) {
2118 if (scrollOffset < rightBound || i == mItems.size() - 1) {
2119 return ii;
2120 }
2121 } else {
2122 return lastItem;
2123 }
2124 first = false;
2125 lastPos = ii.position;
2126 lastOffset = offset;
2127 lastWidth = ii.widthFactor;
2128 lastItem = ii;
2129 }
2130
2131 return lastItem;
2132 }
2133
2134 private int determineTargetPage(int currentPage, float pageOffset, int velocity, int deltaX) {
2135 int targetPage;
2136 if (Math.abs(deltaX) > mFlingDistance && Math.abs(velocity) > mMinimumVelocity) {
2137 targetPage = velocity > 0 ? currentPage : currentPage + 1;
2138 } else {
2139 final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
2140 targetPage = (int) (currentPage + pageOffset + truncator);
2141 }
2142
2143 if (mItems.size() > 0) {
2144 final ItemInfo firstItem = mItems.get(0);
2145 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2146
2147 // Only let the user target pages we have items for
2148 targetPage = Math.max(firstItem.position, Math.min(targetPage, lastItem.position));
2149 }
2150
2151 return targetPage;
2152 }
2153
2154 @Override
2155 public void draw(Canvas canvas) {
2156 super.draw(canvas);
2157 boolean needsInvalidate = false;
2158
2159 final int overScrollMode = getOverScrollMode();
2160 if (overScrollMode == View.OVER_SCROLL_ALWAYS ||
2161 (overScrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS &&
2162 mAdapter != null && mAdapter.getCount() > 1)) {
2163 if (!mLeftEdge.isFinished()) {
2164 final int restoreCount = canvas.save();
2165 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2166 final int width = getWidth();
2167
2168 canvas.rotate(270);
2169 canvas.translate(-height + getPaddingTop(), mFirstOffset * width);
2170 mLeftEdge.setSize(height, width);
2171 needsInvalidate |= mLeftEdge.draw(canvas);
2172 canvas.restoreToCount(restoreCount);
2173 }
2174 if (!mRightEdge.isFinished()) {
2175 final int restoreCount = canvas.save();
2176 final int width = getWidth();
2177 final int height = getHeight() - getPaddingTop() - getPaddingBottom();
2178
2179 canvas.rotate(90);
2180 canvas.translate(-getPaddingTop(), -(mLastOffset + 1) * width);
2181 mRightEdge.setSize(height, width);
2182 needsInvalidate |= mRightEdge.draw(canvas);
2183 canvas.restoreToCount(restoreCount);
2184 }
2185 } else {
2186 mLeftEdge.finish();
2187 mRightEdge.finish();
2188 }
2189
2190 if (needsInvalidate) {
2191 // Keep animating
2192 postInvalidateOnAnimation();
2193 }
2194 }
2195
2196 @Override
2197 protected void onDraw(Canvas canvas) {
2198 super.onDraw(canvas);
2199
2200 // Draw the margin drawable between pages if needed.
2201 if (mPageMargin > 0 && mMarginDrawable != null && mItems.size() > 0 && mAdapter != null) {
2202 final int scrollX = getScrollX();
2203 final int width = getWidth();
2204
2205 final float marginOffset = (float) mPageMargin / width;
2206 int itemIndex = 0;
2207 ItemInfo ii = mItems.get(0);
2208 float offset = ii.offset;
2209 final int itemCount = mItems.size();
2210 final int firstPos = ii.position;
2211 final int lastPos = mItems.get(itemCount - 1).position;
2212 for (int pos = firstPos; pos < lastPos; pos++) {
2213 while (pos > ii.position && itemIndex < itemCount) {
2214 ii = mItems.get(++itemIndex);
2215 }
2216
2217 float drawAt;
2218 if (pos == ii.position) {
2219 drawAt = (ii.offset + ii.widthFactor) * width;
2220 offset = ii.offset + ii.widthFactor + marginOffset;
2221 } else {
2222 float widthFactor = mAdapter.getPageWidth(pos);
2223 drawAt = (offset + widthFactor) * width;
2224 offset += widthFactor + marginOffset;
2225 }
2226
2227 if (drawAt + mPageMargin > scrollX) {
2228 mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
2229 (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
2230 mMarginDrawable.draw(canvas);
2231 }
2232
2233 if (drawAt > scrollX + width) {
2234 break; // No more visible, no sense in continuing
2235 }
2236 }
2237 }
2238 }
2239
2240 /**
2241 * Start a fake drag of the pager.
2242 *
2243 * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
2244 * with the touch scrolling of another view, while still letting the ViewPager
2245 * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
2246 * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
2247 * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
2248 *
2249 * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
2250 * is already in progress, this method will return false.
2251 *
2252 * @return true if the fake drag began successfully, false if it could not be started.
2253 *
2254 * @see #fakeDragBy(float)
2255 * @see #endFakeDrag()
2256 */
2257 public boolean beginFakeDrag() {
2258 if (mIsBeingDragged) {
2259 return false;
2260 }
2261 mFakeDragging = true;
2262 setScrollState(SCROLL_STATE_DRAGGING);
2263 mInitialMotionX = mLastMotionX = 0;
2264 if (mVelocityTracker == null) {
2265 mVelocityTracker = VelocityTracker.obtain();
2266 } else {
2267 mVelocityTracker.clear();
2268 }
2269 final long time = SystemClock.uptimeMillis();
2270 final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
2271 mVelocityTracker.addMovement(ev);
2272 ev.recycle();
2273 mFakeDragBeginTime = time;
2274 return true;
2275 }
2276
2277 /**
2278 * End a fake drag of the pager.
2279 *
2280 * @see #beginFakeDrag()
2281 * @see #fakeDragBy(float)
2282 */
2283 public void endFakeDrag() {
2284 if (!mFakeDragging) {
2285 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2286 }
2287
2288 final VelocityTracker velocityTracker = mVelocityTracker;
2289 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
2290 int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
2291 mPopulatePending = true;
2292 final int width = getClientWidth();
2293 final int scrollX = getScrollX();
2294 final ItemInfo ii = infoForCurrentScrollPosition();
2295 final int currentPage = ii.position;
2296 final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
2297 final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
2298 int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
2299 totalDelta);
2300 setCurrentItemInternal(nextPage, true, true, initialVelocity);
2301 endDrag();
2302
2303 mFakeDragging = false;
2304 }
2305
2306 /**
2307 * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
2308 *
2309 * @param xOffset Offset in pixels to drag by.
2310 * @see #beginFakeDrag()
2311 * @see #endFakeDrag()
2312 */
2313 public void fakeDragBy(float xOffset) {
2314 if (!mFakeDragging) {
2315 throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
2316 }
2317
2318 mLastMotionX += xOffset;
2319
2320 float oldScrollX = getScrollX();
2321 float scrollX = oldScrollX - xOffset;
2322 final int width = getClientWidth();
2323
2324 float leftBound = width * mFirstOffset;
2325 float rightBound = width * mLastOffset;
2326
2327 final ItemInfo firstItem = mItems.get(0);
2328 final ItemInfo lastItem = mItems.get(mItems.size() - 1);
2329 if (firstItem.position != 0) {
2330 leftBound = firstItem.offset * width;
2331 }
2332 if (lastItem.position != mAdapter.getCount() - 1) {
2333 rightBound = lastItem.offset * width;
2334 }
2335
2336 if (scrollX < leftBound) {
2337 scrollX = leftBound;
2338 } else if (scrollX > rightBound) {
2339 scrollX = rightBound;
2340 }
2341 // Don't lose the rounded component
2342 mLastMotionX += scrollX - (int) scrollX;
2343 scrollTo((int) scrollX, getScrollY());
2344 pageScrolled((int) scrollX);
2345
2346 // Synthesize an event for the VelocityTracker.
2347 final long time = SystemClock.uptimeMillis();
2348 final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
2349 mLastMotionX, 0, 0);
2350 mVelocityTracker.addMovement(ev);
2351 ev.recycle();
2352 }
2353
2354 /**
2355 * Returns true if a fake drag is in progress.
2356 *
2357 * @return true if currently in a fake drag, false otherwise.
2358 *
2359 * @see #beginFakeDrag()
2360 * @see #fakeDragBy(float)
2361 * @see #endFakeDrag()
2362 */
2363 public boolean isFakeDragging() {
2364 return mFakeDragging;
2365 }
2366
2367 private void onSecondaryPointerUp(MotionEvent ev) {
2368 final int pointerIndex = ev.getActionIndex();
2369 final int pointerId = ev.getPointerId(pointerIndex);
2370 if (pointerId == mActivePointerId) {
2371 // This was our active pointer going up. Choose a new
2372 // active pointer and adjust accordingly.
2373 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
2374 mLastMotionX = ev.getX(newPointerIndex);
2375 mActivePointerId = ev.getPointerId(newPointerIndex);
2376 if (mVelocityTracker != null) {
2377 mVelocityTracker.clear();
2378 }
2379 }
2380 }
2381
2382 private void endDrag() {
2383 mIsBeingDragged = false;
2384 mIsUnableToDrag = false;
2385
2386 if (mVelocityTracker != null) {
2387 mVelocityTracker.recycle();
2388 mVelocityTracker = null;
2389 }
2390 }
2391
2392 private void setScrollingCacheEnabled(boolean enabled) {
2393 if (mScrollingCacheEnabled != enabled) {
2394 mScrollingCacheEnabled = enabled;
2395 if (USE_CACHE) {
2396 final int size = getChildCount();
2397 for (int i = 0; i < size; ++i) {
2398 final View child = getChildAt(i);
2399 if (child.getVisibility() != GONE) {
2400 child.setDrawingCacheEnabled(enabled);
2401 }
2402 }
2403 }
2404 }
2405 }
2406
2407 public boolean canScrollHorizontally(int direction) {
2408 if (mAdapter == null) {
2409 return false;
2410 }
2411
2412 final int width = getClientWidth();
2413 final int scrollX = getScrollX();
2414 if (direction < 0) {
2415 return (scrollX > (int) (width * mFirstOffset));
2416 } else if (direction > 0) {
2417 return (scrollX < (int) (width * mLastOffset));
2418 } else {
2419 return false;
2420 }
2421 }
2422
2423 /**
2424 * Tests scrollability within child views of v given a delta of dx.
2425 *
2426 * @param v View to test for horizontal scrollability
2427 * @param checkV Whether the view v passed should itself be checked for scrollability (true),
2428 * or just its children (false).
2429 * @param dx Delta scrolled in pixels
2430 * @param x X coordinate of the active touch point
2431 * @param y Y coordinate of the active touch point
2432 * @return true if child views of v can be scrolled by delta of dx.
2433 */
2434 protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
2435 if (v instanceof ViewGroup) {
2436 final ViewGroup group = (ViewGroup) v;
2437 final int scrollX = v.getScrollX();
2438 final int scrollY = v.getScrollY();
2439 final int count = group.getChildCount();
2440 // Count backwards - let topmost views consume scroll distance first.
2441 for (int i = count - 1; i >= 0; i--) {
2442 // TODO: Add versioned support here for transformed views.
2443 // This will not work for transformed views in Honeycomb+
2444 final View child = group.getChildAt(i);
2445 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
2446 y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
2447 canScroll(child, true, dx, x + scrollX - child.getLeft(),
2448 y + scrollY - child.getTop())) {
2449 return true;
2450 }
2451 }
2452 }
2453
2454 return checkV && v.canScrollHorizontally(-dx);
2455 }
2456
2457 @Override
2458 public boolean dispatchKeyEvent(KeyEvent event) {
2459 // Let the focused view and/or our descendants get the key first
2460 return super.dispatchKeyEvent(event) || executeKeyEvent(event);
2461 }
2462
2463 /**
2464 * You can call this function yourself to have the scroll view perform
2465 * scrolling from a key event, just as if the event had been dispatched to
2466 * it by the view hierarchy.
2467 *
2468 * @param event The key event to execute.
2469 * @return Return true if the event was handled, else false.
2470 */
2471 public boolean executeKeyEvent(KeyEvent event) {
2472 boolean handled = false;
2473 if (event.getAction() == KeyEvent.ACTION_DOWN) {
2474 switch (event.getKeyCode()) {
2475 case KeyEvent.KEYCODE_DPAD_LEFT:
2476 handled = arrowScroll(FOCUS_LEFT);
2477 break;
2478 case KeyEvent.KEYCODE_DPAD_RIGHT:
2479 handled = arrowScroll(FOCUS_RIGHT);
2480 break;
2481 case KeyEvent.KEYCODE_TAB:
2482 if (event.hasNoModifiers()) {
2483 handled = arrowScroll(FOCUS_FORWARD);
2484 } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
2485 handled = arrowScroll(FOCUS_BACKWARD);
2486 }
2487 break;
2488 }
2489 }
2490 return handled;
2491 }
2492
2493 public boolean arrowScroll(int direction) {
2494 View currentFocused = findFocus();
2495 if (currentFocused == this) {
2496 currentFocused = null;
2497 } else if (currentFocused != null) {
2498 boolean isChild = false;
2499 for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
2500 parent = parent.getParent()) {
2501 if (parent == this) {
2502 isChild = true;
2503 break;
2504 }
2505 }
2506 if (!isChild) {
2507 // This would cause the focus search down below to fail in fun ways.
2508 final StringBuilder sb = new StringBuilder();
2509 sb.append(currentFocused.getClass().getSimpleName());
2510 for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup;
2511 parent = parent.getParent()) {
2512 sb.append(" => ").append(parent.getClass().getSimpleName());
2513 }
2514 Log.e(TAG, "arrowScroll tried to find focus based on non-child " +
2515 "current focused view " + sb.toString());
2516 currentFocused = null;
2517 }
2518 }
2519
2520 boolean handled = false;
2521
2522 View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
2523 direction);
2524 if (nextFocused != null && nextFocused != currentFocused) {
2525 if (direction == View.FOCUS_LEFT) {
2526 // If there is nothing to the left, or this is causing us to
2527 // jump to the right, then what we really want to do is page left.
2528 final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
2529 final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
2530 if (currentFocused != null && nextLeft >= currLeft) {
2531 handled = pageLeft();
2532 } else {
2533 handled = nextFocused.requestFocus();
2534 }
2535 } else if (direction == View.FOCUS_RIGHT) {
2536 // If there is nothing to the right, or this is causing us to
2537 // jump to the left, then what we really want to do is page right.
2538 final int nextLeft = getChildRectInPagerCoordinates(mTempRect, nextFocused).left;
2539 final int currLeft = getChildRectInPagerCoordinates(mTempRect, currentFocused).left;
2540 if (currentFocused != null && nextLeft <= currLeft) {
2541 handled = pageRight();
2542 } else {
2543 handled = nextFocused.requestFocus();
2544 }
2545 }
2546 } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
2547 // Trying to move left and nothing there; try to page.
2548 handled = pageLeft();
2549 } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
2550 // Trying to move right and nothing there; try to page.
2551 handled = pageRight();
2552 }
2553 if (handled) {
2554 playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
2555 }
2556 return handled;
2557 }
2558
2559 private Rect getChildRectInPagerCoordinates(Rect outRect, View child) {
2560 if (outRect == null) {
2561 outRect = new Rect();
2562 }
2563 if (child == null) {
2564 outRect.set(0, 0, 0, 0);
2565 return outRect;
2566 }
2567 outRect.left = child.getLeft();
2568 outRect.right = child.getRight();
2569 outRect.top = child.getTop();
2570 outRect.bottom = child.getBottom();
2571
2572 ViewParent parent = child.getParent();
2573 while (parent instanceof ViewGroup && parent != this) {
2574 final ViewGroup group = (ViewGroup) parent;
2575 outRect.left += group.getLeft();
2576 outRect.right += group.getRight();
2577 outRect.top += group.getTop();
2578 outRect.bottom += group.getBottom();
2579
2580 parent = group.getParent();
2581 }
2582 return outRect;
2583 }
2584
2585 boolean pageLeft() {
2586 if (mCurItem > 0) {
2587 setCurrentItem(mCurItem-1, true);
2588 return true;
2589 }
2590 return false;
2591 }
2592
2593 boolean pageRight() {
2594 if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
2595 setCurrentItem(mCurItem+1, true);
2596 return true;
2597 }
2598 return false;
2599 }
2600
2601 /**
2602 * We only want the current page that is being shown to be focusable.
2603 */
2604 @Override
2605 public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
2606 final int focusableCount = views.size();
2607
2608 final int descendantFocusability = getDescendantFocusability();
2609
2610 if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
2611 for (int i = 0; i < getChildCount(); i++) {
2612 final View child = getChildAt(i);
2613 if (child.getVisibility() == VISIBLE) {
2614 ItemInfo ii = infoForChild(child);
2615 if (ii != null && ii.position == mCurItem) {
2616 child.addFocusables(views, direction, focusableMode);
2617 }
2618 }
2619 }
2620 }
2621
2622 // we add ourselves (if focusable) in all cases except for when we are
2623 // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable. this is
2624 // to avoid the focus search finding layouts when a more precise search
2625 // among the focusable children would be more interesting.
2626 if (
2627 descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
2628 // No focusable descendants
2629 (focusableCount == views.size())) {
2630 // Note that we can't call the superclass here, because it will
2631 // add all views in. So we need to do the same thing View does.
2632 if (!isFocusable()) {
2633 return;
2634 }
2635 if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
2636 isInTouchMode() && !isFocusableInTouchMode()) {
2637 return;
2638 }
2639 if (views != null) {
2640 views.add(this);
2641 }
2642 }
2643 }
2644
2645 /**
2646 * We only want the current page that is being shown to be touchable.
2647 */
2648 @Override
2649 public void addTouchables(ArrayList<View> views) {
2650 // Note that we don't call super.addTouchables(), which means that
2651 // we don't call View.addTouchables(). This is okay because a ViewPager
2652 // is itself not touchable.
2653 for (int i = 0; i < getChildCount(); i++) {
2654 final View child = getChildAt(i);
2655 if (child.getVisibility() == VISIBLE) {
2656 ItemInfo ii = infoForChild(child);
2657 if (ii != null && ii.position == mCurItem) {
2658 child.addTouchables(views);
2659 }
2660 }
2661 }
2662 }
2663
2664 /**
2665 * We only want the current page that is being shown to be focusable.
2666 */
2667 @Override
2668 protected boolean onRequestFocusInDescendants(int direction,
2669 Rect previouslyFocusedRect) {
2670 int index;
2671 int increment;
2672 int end;
2673 int count = getChildCount();
2674 if ((direction & FOCUS_FORWARD) != 0) {
2675 index = 0;
2676 increment = 1;
2677 end = count;
2678 } else {
2679 index = count - 1;
2680 increment = -1;
2681 end = -1;
2682 }
2683 for (int i = index; i != end; i += increment) {
2684 View child = getChildAt(i);
2685 if (child.getVisibility() == VISIBLE) {
2686 ItemInfo ii = infoForChild(child);
2687 if (ii != null && ii.position == mCurItem) {
2688 if (child.requestFocus(direction, previouslyFocusedRect)) {
2689 return true;
2690 }
2691 }
2692 }
2693 }
2694 return false;
2695 }
2696
2697 @Override
2698 public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
2699 // Dispatch scroll events from this ViewPager.
2700 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
2701 return super.dispatchPopulateAccessibilityEvent(event);
2702 }
2703
2704 // Dispatch all other accessibility events from the current page.
2705 final int childCount = getChildCount();
2706 for (int i = 0; i < childCount; i++) {
2707 final View child = getChildAt(i);
2708 if (child.getVisibility() == VISIBLE) {
2709 final ItemInfo ii = infoForChild(child);
2710 if (ii != null && ii.position == mCurItem &&
2711 child.dispatchPopulateAccessibilityEvent(event)) {
2712 return true;
2713 }
2714 }
2715 }
2716
2717 return false;
2718 }
2719
2720 @Override
2721 protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
2722 return new LayoutParams();
2723 }
2724
2725 @Override
2726 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
2727 return generateDefaultLayoutParams();
2728 }
2729
2730 @Override
2731 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
2732 return p instanceof LayoutParams && super.checkLayoutParams(p);
2733 }
2734
2735 @Override
2736 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
2737 return new LayoutParams(getContext(), attrs);
2738 }
2739
2740 class MyAccessibilityDelegate extends AccessibilityDelegate {
2741
2742 @Override
2743 public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
2744 super.onInitializeAccessibilityEvent(host, event);
2745 event.setClassName(ViewPager.class.getName());
2746 final AccessibilityRecord record = AccessibilityRecord.obtain();
2747 record.setScrollable(canScroll());
2748 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED
2749 && mAdapter != null) {
2750 record.setItemCount(mAdapter.getCount());
2751 record.setFromIndex(mCurItem);
2752 record.setToIndex(mCurItem);
2753 }
2754 }
2755
2756 @Override
2757 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
2758 super.onInitializeAccessibilityNodeInfo(host, info);
2759 info.setClassName(ViewPager.class.getName());
2760 info.setScrollable(canScroll());
2761 if (canScrollHorizontally(1)) {
2762 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
2763 }
2764 if (canScrollHorizontally(-1)) {
2765 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
2766 }
2767 }
2768
2769 @Override
2770 public boolean performAccessibilityAction(View host, int action, Bundle args) {
2771 if (super.performAccessibilityAction(host, action, args)) {
2772 return true;
2773 }
2774 switch (action) {
2775 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
2776 if (canScrollHorizontally(1)) {
2777 setCurrentItem(mCurItem + 1);
2778 return true;
2779 }
2780 } return false;
2781 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
2782 if (canScrollHorizontally(-1)) {
2783 setCurrentItem(mCurItem - 1);
2784 return true;
2785 }
2786 } return false;
2787 }
2788 return false;
2789 }
2790
2791 private boolean canScroll() {
2792 return (mAdapter != null) && (mAdapter.getCount() > 1);
2793 }
2794 }
2795
2796 private class PagerObserver extends DataSetObserver {
2797 @Override
2798 public void onChanged() {
2799 dataSetChanged();
2800 }
2801 @Override
2802 public void onInvalidated() {
2803 dataSetChanged();
2804 }
2805 }
2806
2807 /**
2808 * Layout parameters that should be supplied for views added to a
2809 * ViewPager.
2810 */
2811 public static class LayoutParams extends ViewGroup.LayoutParams {
2812 /**
2813 * true if this view is a decoration on the pager itself and not
2814 * a view supplied by the adapter.
2815 */
2816 public boolean isDecor;
2817
2818 /**
2819 * Gravity setting for use on decor views only:
2820 * Where to position the view page within the overall ViewPager
2821 * container; constants are defined in {@link android.view.Gravity}.
2822 */
2823 public int gravity;
2824
2825 /**
2826 * Width as a 0-1 multiplier of the measured pager width
2827 */
2828 float widthFactor = 0.f;
2829
2830 /**
2831 * true if this view was added during layout and needs to be measured
2832 * before being positioned.
2833 */
2834 boolean needsMeasure;
2835
2836 /**
2837 * Adapter position this view is for if !isDecor
2838 */
2839 int position;
2840
2841 /**
2842 * Current child index within the ViewPager that this view occupies
2843 */
2844 int childIndex;
2845
2846 public LayoutParams() {
2847 super(FILL_PARENT, FILL_PARENT);
2848 }
2849
2850 public LayoutParams(Context context, AttributeSet attrs) {
2851 super(context, attrs);
2852
2853 final TypedArray a = context.obtainStyledAttributes(attrs, LAYOUT_ATTRS);
2854 gravity = a.getInteger(0, Gravity.TOP);
2855 a.recycle();
2856 }
2857 }
2858
2859 static class ViewPositionComparator implements Comparator<View> {
2860 @Override
2861 public int compare(View lhs, View rhs) {
2862 final LayoutParams llp = (LayoutParams) lhs.getLayoutParams();
2863 final LayoutParams rlp = (LayoutParams) rhs.getLayoutParams();
2864 if (llp.isDecor != rlp.isDecor) {
2865 return llp.isDecor ? 1 : -1;
2866 }
2867 return llp.position - rlp.position;
2868 }
2869 }
2870}