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