ViewPager.java revision 2a4d8518f36346ea25a22a736453ff28f2954165
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.view;
18
19import android.content.Context;
20import android.graphics.Rect;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.os.SystemClock;
24import android.support.v4.os.ParcelableCompat;
25import android.support.v4.os.ParcelableCompatCreatorCallbacks;
26import android.util.AttributeSet;
27import android.util.Log;
28import android.view.FocusFinder;
29import android.view.KeyEvent;
30import android.view.MotionEvent;
31import android.view.SoundEffectConstants;
32import android.view.VelocityTracker;
33import android.view.View;
34import android.view.ViewConfiguration;
35import android.view.ViewGroup;
36import android.view.accessibility.AccessibilityEvent;
37import android.widget.Scroller;
38
39import java.util.ArrayList;
40import java.util.Collections;
41import java.util.Comparator;
42
43/**
44 * Layout manager that allows the user to flip left and right
45 * through pages of data.  You supply an implementation of a
46 * {@link PagerAdapter} to generate the pages that the view shows.
47 *
48 * <p>Note this class is currently under early design and
49 * development.  The API will likely change in later updates of
50 * the compatibility library, requiring changes to the source code
51 * of apps when they are compiled against the newer version.</p>
52 */
53public class ViewPager extends ViewGroup {
54    private static final String TAG = "ViewPager";
55    private static final boolean DEBUG = false;
56
57    private static final boolean USE_CACHE = false;
58
59    static class ItemInfo {
60        Object object;
61        int position;
62        boolean scrolling;
63    }
64
65    private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
66        @Override
67        public int compare(ItemInfo lhs, ItemInfo rhs) {
68            return lhs.position - rhs.position;
69        }};
70
71    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
72
73    private PagerAdapter mAdapter;
74    private int mCurItem;   // Index of currently displayed page.
75    private int mRestoredCurItem = -1;
76    private Parcelable mRestoredAdapterState = null;
77    private ClassLoader mRestoredClassLoader = null;
78    private Scroller mScroller;
79    private PagerAdapter.DataSetObserver mObserver;
80
81    private int mChildWidthMeasureSpec;
82    private int mChildHeightMeasureSpec;
83    private boolean mInLayout;
84
85    private boolean mScrollingCacheEnabled;
86
87    private boolean mPopulatePending;
88    private boolean mScrolling;
89
90    private boolean mIsBeingDragged;
91    private boolean mIsUnableToDrag;
92    private int mTouchSlop;
93    private float mInitialMotionX;
94    /**
95     * Position of the last motion event.
96     */
97    private float mLastMotionX;
98    private float mLastMotionY;
99    /**
100     * ID of the active pointer. This is used to retain consistency during
101     * drags/flings if multiple pointers are used.
102     */
103    private int mActivePointerId = INVALID_POINTER;
104    /**
105     * Sentinel value for no current active pointer.
106     * Used by {@link #mActivePointerId}.
107     */
108    private static final int INVALID_POINTER = -1;
109
110    /**
111     * Determines speed during touch scrolling
112     */
113    private VelocityTracker mVelocityTracker;
114    private int mMinimumVelocity;
115    private int mMaximumVelocity;
116
117    private boolean mFakeDragging;
118    private long mFakeDragBeginTime;
119
120    private boolean mFirstLayout = true;
121
122    private OnPageChangeListener mOnPageChangeListener;
123
124    /**
125     * Indicates that the pager is in an idle, settled state. The current page
126     * is fully in view and no animation is in progress.
127     */
128    public static final int SCROLL_STATE_IDLE = 0;
129
130    /**
131     * Indicates that the pager is currently being dragged by the user.
132     */
133    public static final int SCROLL_STATE_DRAGGING = 1;
134
135    /**
136     * Indicates that the pager is in the process of settling to a final position.
137     */
138    public static final int SCROLL_STATE_SETTLING = 2;
139
140    private int mScrollState = SCROLL_STATE_IDLE;
141
142    /**
143     * Callback interface for responding to changing state of the selected page.
144     */
145    public interface OnPageChangeListener {
146
147        /**
148         * This method will be invoked when the current page is scrolled, either as part
149         * of a programmatically initiated smooth scroll or a user initiated touch scroll.
150         *
151         * @param position Position index of the first page currently being displayed.
152         *                 Page position+1 will be visible if positionOffset is nonzero.
153         * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
154         * @param positionOffsetPixels Value in pixels indicating the offset from position.
155         */
156        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
157
158        /**
159         * This method will be invoked when a new page becomes selected. Animation is not
160         * necessarily complete.
161         *
162         * @param position Position index of the new selected page.
163         */
164        public void onPageSelected(int position);
165
166        /**
167         * Called when the scroll state changes. Useful for discovering when the user
168         * begins dragging, when the pager is automatically settling to the current page,
169         * or when it is fully stopped/idle.
170         *
171         * @param state The new scroll state.
172         * @see ViewPager#SCROLL_STATE_IDLE
173         * @see ViewPager#SCROLL_STATE_DRAGGING
174         * @see ViewPager#SCROLL_STATE_SETTLING
175         */
176        public void onPageScrollStateChanged(int state);
177    }
178
179    /**
180     * Simple implementation of the {@link OnPageChangeListener} interface with stub
181     * implementations of each method. Extend this if you do not intend to override
182     * every method of {@link OnPageChangeListener}.
183     */
184    public static class SimpleOnPageChangeListener implements OnPageChangeListener {
185        @Override
186        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
187            // This space for rent
188        }
189
190        @Override
191        public void onPageSelected(int position) {
192            // This space for rent
193        }
194
195        @Override
196        public void onPageScrollStateChanged(int state) {
197            // This space for rent
198        }
199    }
200
201    public ViewPager(Context context) {
202        super(context);
203        initViewPager();
204    }
205
206    public ViewPager(Context context, AttributeSet attrs) {
207        super(context, attrs);
208        initViewPager();
209    }
210
211    void initViewPager() {
212        setWillNotDraw(false);
213        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
214        setFocusable(true);
215        mScroller = new Scroller(getContext());
216        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
217        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
218        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
219        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
220    }
221
222    private void setScrollState(int newState) {
223        if (mScrollState == newState) {
224            return;
225        }
226
227        mScrollState = newState;
228        if (mOnPageChangeListener != null) {
229            mOnPageChangeListener.onPageScrollStateChanged(newState);
230        }
231    }
232
233    public void setAdapter(PagerAdapter adapter) {
234        if (mAdapter != null) {
235            mAdapter.setDataSetObserver(null);
236            mAdapter.startUpdate(this);
237            for (int i = 0; i < mItems.size(); i++) {
238                final ItemInfo ii = mItems.get(i);
239                mAdapter.destroyItem(this, ii.position, ii.object);
240            }
241            mAdapter.finishUpdate(this);
242            mItems.clear();
243            removeAllViews();
244            mCurItem = 0;
245            scrollTo(0, 0);
246        }
247
248        mAdapter = adapter;
249
250        if (mAdapter != null) {
251            if (mObserver == null) {
252                mObserver = new DataSetObserver();
253            }
254            mAdapter.setDataSetObserver(mObserver);
255            mPopulatePending = false;
256            if (mRestoredCurItem >= 0) {
257                mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
258                setCurrentItemInternal(mRestoredCurItem, false, true);
259                mRestoredCurItem = -1;
260                mRestoredAdapterState = null;
261                mRestoredClassLoader = null;
262            } else {
263                populate();
264            }
265        }
266    }
267
268    public PagerAdapter getAdapter() {
269        return mAdapter;
270    }
271
272    /**
273     * Set the currently selected page. If the ViewPager has already been through its first
274     * layout there will be a smooth animated transition between the current item and the
275     * specified item.
276     *
277     * @param item Item index to select
278     */
279    public void setCurrentItem(int item) {
280        mPopulatePending = false;
281        setCurrentItemInternal(item, !mFirstLayout, false);
282    }
283
284    /**
285     * Set the currently selected page.
286     *
287     * @param item Item index to select
288     * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
289     */
290    public void setCurrentItem(int item, boolean smoothScroll) {
291        mPopulatePending = false;
292        setCurrentItemInternal(item, smoothScroll, false);
293    }
294
295    public int getCurrentItem() {
296        return mCurItem;
297    }
298
299    void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
300        if (mAdapter == null || mAdapter.getCount() <= 0) {
301            setScrollingCacheEnabled(false);
302            return;
303        }
304        if (!always && mCurItem == item && mItems.size() != 0) {
305            setScrollingCacheEnabled(false);
306            return;
307        }
308        if (item < 0) {
309            item = 0;
310        } else if (item >= mAdapter.getCount()) {
311            item = mAdapter.getCount() - 1;
312        }
313        if (item > (mCurItem+1) || item < (mCurItem-1)) {
314            // We are doing a jump by more than one page.  To avoid
315            // glitches, we want to keep all current pages in the view
316            // until the scroll ends.
317            for (int i=0; i<mItems.size(); i++) {
318                mItems.get(i).scrolling = true;
319            }
320        }
321        final boolean dispatchSelected = mCurItem != item;
322        mCurItem = item;
323        populate();
324        if (smoothScroll) {
325            smoothScrollTo(getWidth()*item, 0);
326            if (dispatchSelected && mOnPageChangeListener != null) {
327                mOnPageChangeListener.onPageSelected(item);
328            }
329        } else {
330            if (dispatchSelected && mOnPageChangeListener != null) {
331                mOnPageChangeListener.onPageSelected(item);
332            }
333            completeScroll();
334            scrollTo(getWidth()*item, 0);
335        }
336    }
337
338    public void setOnPageChangeListener(OnPageChangeListener listener) {
339        mOnPageChangeListener = listener;
340    }
341
342    /**
343     * Like {@link View#scrollBy}, but scroll smoothly instead of immediately.
344     *
345     * @param x the number of pixels to scroll by on the X axis
346     * @param y the number of pixels to scroll by on the Y axis
347     */
348    void smoothScrollTo(int x, int y) {
349        if (getChildCount() == 0) {
350            // Nothing to do.
351            setScrollingCacheEnabled(false);
352            return;
353        }
354        int sx = getScrollX();
355        int sy = getScrollY();
356        int dx = x - sx;
357        int dy = y - sy;
358        if (dx == 0 && dy == 0) {
359            completeScroll();
360            return;
361        }
362
363        setScrollingCacheEnabled(true);
364        mScrolling = true;
365        setScrollState(SCROLL_STATE_SETTLING);
366        mScroller.startScroll(sx, sy, dx, dy);
367        invalidate();
368    }
369
370    void addNewItem(int position, int index) {
371        ItemInfo ii = new ItemInfo();
372        ii.position = position;
373        ii.object = mAdapter.instantiateItem(this, position);
374        if (index < 0) {
375            mItems.add(ii);
376        } else {
377            mItems.add(index, ii);
378        }
379    }
380
381    void dataSetChanged() {
382        // This method only gets called if our observer is attached, so mAdapter is non-null.
383
384        boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
385        int newCurrItem = -1;
386
387        for (int i = 0; i < mItems.size(); i++) {
388            final ItemInfo ii = mItems.get(i);
389            final int newPos = mAdapter.getItemPosition(ii.object);
390
391            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
392                continue;
393            }
394
395            if (newPos == PagerAdapter.POSITION_NONE) {
396                mItems.remove(i);
397                i--;
398                mAdapter.destroyItem(this, ii.position, ii.object);
399                needPopulate = true;
400
401                if (mCurItem == ii.position) {
402                    // Keep the current item in the valid range
403                    newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
404                }
405                continue;
406            }
407
408            if (ii.position != newPos) {
409                if (ii.position == mCurItem) {
410                    // Our current item changed position. Follow it.
411                    newCurrItem = newPos;
412                }
413
414                ii.position = newPos;
415                needPopulate = true;
416            }
417        }
418
419        Collections.sort(mItems, COMPARATOR);
420
421        if (newCurrItem >= 0) {
422            // TODO This currently causes a jump.
423            setCurrentItemInternal(newCurrItem, false, true);
424            needPopulate = true;
425        }
426        if (needPopulate) {
427            populate();
428            requestLayout();
429        }
430    }
431
432    void populate() {
433        if (mAdapter == null) {
434            return;
435        }
436
437        // Bail now if we are waiting to populate.  This is to hold off
438        // on creating views from the time the user releases their finger to
439        // fling to a new position until we have finished the scroll to
440        // that position, avoiding glitches from happening at that point.
441        if (mPopulatePending) {
442            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
443            return;
444        }
445
446        // Also, don't populate until we are attached to a window.  This is to
447        // avoid trying to populate before we have restored our view hierarchy
448        // state and conflicting with what is restored.
449        if (getWindowToken() == null) {
450            return;
451        }
452
453        mAdapter.startUpdate(this);
454
455        final int startPos = mCurItem > 0 ? mCurItem - 1 : mCurItem;
456        final int N = mAdapter.getCount();
457        final int endPos = mCurItem < (N-1) ? mCurItem+1 : N-1;
458
459        if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);
460
461        // Add and remove pages in the existing list.
462        int lastPos = -1;
463        for (int i=0; i<mItems.size(); i++) {
464            ItemInfo ii = mItems.get(i);
465            if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) {
466                if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i);
467                mItems.remove(i);
468                i--;
469                mAdapter.destroyItem(this, ii.position, ii.object);
470            } else if (lastPos < endPos && ii.position > startPos) {
471                // The next item is outside of our range, but we have a gap
472                // between it and the last item where we want to have a page
473                // shown.  Fill in the gap.
474                lastPos++;
475                if (lastPos < startPos) {
476                    lastPos = startPos;
477                }
478                while (lastPos <= endPos && lastPos < ii.position) {
479                    if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i);
480                    addNewItem(lastPos, i);
481                    lastPos++;
482                    i++;
483                }
484            }
485            lastPos = ii.position;
486        }
487
488        // Add any new pages we need at the end.
489        lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1;
490        if (lastPos < endPos) {
491            lastPos++;
492            lastPos = lastPos > startPos ? lastPos : startPos;
493            while (lastPos <= endPos) {
494                if (DEBUG) Log.i(TAG, "appending: " + lastPos);
495                addNewItem(lastPos, -1);
496                lastPos++;
497            }
498        }
499
500        if (DEBUG) {
501            Log.i(TAG, "Current page list:");
502            for (int i=0; i<mItems.size(); i++) {
503                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
504            }
505        }
506
507        ItemInfo curItem = null;
508        for (int i=0; i<mItems.size(); i++) {
509            if (mItems.get(i).position == mCurItem) {
510                curItem = mItems.get(i);
511                break;
512            }
513        }
514        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
515
516        mAdapter.finishUpdate(this);
517
518        if (hasFocus()) {
519            View currentFocused = findFocus();
520            ItemInfo ii = currentFocused != null ? infoForChild(currentFocused) : null;
521            if (ii == null || ii.position != mCurItem) {
522                for (int i=0; i<getChildCount(); i++) {
523                    View child = getChildAt(i);
524                    ii = infoForChild(child);
525                    if (ii != null && ii.position == mCurItem) {
526                        if (child.requestFocus(FOCUS_FORWARD)) {
527                            break;
528                        }
529                    }
530                }
531            }
532        }
533    }
534
535    public static class SavedState extends BaseSavedState {
536        int position;
537        Parcelable adapterState;
538        ClassLoader loader;
539
540        public SavedState(Parcelable superState) {
541            super(superState);
542        }
543
544        @Override
545        public void writeToParcel(Parcel out, int flags) {
546            super.writeToParcel(out, flags);
547            out.writeInt(position);
548            out.writeParcelable(adapterState, flags);
549        }
550
551        @Override
552        public String toString() {
553            return "FragmentPager.SavedState{"
554                    + Integer.toHexString(System.identityHashCode(this))
555                    + " position=" + position + "}";
556        }
557
558        public static final Parcelable.Creator<SavedState> CREATOR
559                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
560                    @Override
561                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
562                        return new SavedState(in, loader);
563                    }
564                    @Override
565                    public SavedState[] newArray(int size) {
566                        return new SavedState[size];
567                    }
568                });
569
570        SavedState(Parcel in, ClassLoader loader) {
571            super(in);
572            if (loader == null) {
573                loader = getClass().getClassLoader();
574            }
575            position = in.readInt();
576            adapterState = in.readParcelable(loader);
577            this.loader = loader;
578        }
579    }
580
581    @Override
582    public Parcelable onSaveInstanceState() {
583        Parcelable superState = super.onSaveInstanceState();
584        SavedState ss = new SavedState(superState);
585        ss.position = mCurItem;
586        if (mAdapter != null) {
587            ss.adapterState = mAdapter.saveState();
588        }
589        return ss;
590    }
591
592    @Override
593    public void onRestoreInstanceState(Parcelable state) {
594        if (!(state instanceof SavedState)) {
595            super.onRestoreInstanceState(state);
596            return;
597        }
598
599        SavedState ss = (SavedState)state;
600        super.onRestoreInstanceState(ss.getSuperState());
601
602        if (mAdapter != null) {
603            mAdapter.restoreState(ss.adapterState, ss.loader);
604            setCurrentItemInternal(ss.position, false, true);
605        } else {
606            mRestoredCurItem = ss.position;
607            mRestoredAdapterState = ss.adapterState;
608            mRestoredClassLoader = ss.loader;
609        }
610    }
611
612    @Override
613    public void addView(View child, int index, LayoutParams params) {
614        if (mInLayout) {
615            addViewInLayout(child, index, params);
616            child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
617        } else {
618            super.addView(child, index, params);
619        }
620
621        if (USE_CACHE) {
622            if (child.getVisibility() != GONE) {
623                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
624            } else {
625                child.setDrawingCacheEnabled(false);
626            }
627        }
628    }
629
630    ItemInfo infoForChild(View child) {
631        for (int i=0; i<mItems.size(); i++) {
632            ItemInfo ii = mItems.get(i);
633            if (mAdapter.isViewFromObject(child, ii.object)) {
634                return ii;
635            }
636        }
637        return null;
638    }
639
640    @Override
641    protected void onAttachedToWindow() {
642        super.onAttachedToWindow();
643        mFirstLayout = true;
644        if (mAdapter != null) {
645            populate();
646        }
647    }
648
649    @Override
650    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
651        // For simple implementation, or internal size is always 0.
652        // We depend on the container to specify the layout size of
653        // our view.  We can't really know what it is since we will be
654        // adding and removing different arbitrary views and do not
655        // want the layout to change as this happens.
656        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
657                getDefaultSize(0, heightMeasureSpec));
658
659        // Children are just made to fill our space.
660        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
661                getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
662        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
663                getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
664
665        // Make sure we have created all fragments that we need to have shown.
666        mInLayout = true;
667        populate();
668        mInLayout = false;
669
670        // Make sure all children have been properly measured.
671        final int size = getChildCount();
672        for (int i = 0; i < size; ++i) {
673            final View child = getChildAt(i);
674            if (child.getVisibility() != GONE) {
675                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
676		        + ": " + mChildWidthMeasureSpec);
677                child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
678            }
679        }
680    }
681
682    @Override
683    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
684        super.onSizeChanged(w, h, oldw, oldh);
685
686        // Make sure scroll position is set correctly.
687        if (w != oldw) {
688            if (oldw > 0) {
689                final int oldScrollPos = getScrollX();
690                final int oldScrollItem = oldScrollPos / oldw;
691                final float scrollOffset = (float) (oldScrollPos % oldw) / oldw;
692                final int scrollPos = (int) ((oldScrollItem + scrollOffset) * w);
693                scrollTo(scrollPos, getScrollY());
694                if (!mScroller.isFinished()) {
695                    // We now return to your regularly scheduled scroll, already in progress.
696                    final int newDuration = mScroller.getDuration() - mScroller.timePassed();
697                    mScroller.startScroll(scrollPos, 0, mCurItem * w, 0, newDuration);
698                }
699            } else {
700                int scrollPos = mCurItem * w;
701                if (scrollPos != getScrollX()) {
702                    completeScroll();
703                    scrollTo(scrollPos, getScrollY());
704                }
705            }
706        }
707    }
708
709    @Override
710    protected void onLayout(boolean changed, int l, int t, int r, int b) {
711        mInLayout = true;
712        populate();
713        mInLayout = false;
714
715        final int count = getChildCount();
716        final int width = r-l;
717
718        for (int i = 0; i < count; i++) {
719            View child = getChildAt(i);
720            ItemInfo ii;
721            if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
722                int loff = width*ii.position;
723                int childLeft = getPaddingLeft() + loff;
724                int childTop = getPaddingTop();
725                if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
726		        + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
727		        + "x" + child.getMeasuredHeight());
728                child.layout(childLeft, childTop,
729                        childLeft + child.getMeasuredWidth(),
730                        childTop + child.getMeasuredHeight());
731            }
732        }
733        mFirstLayout = false;
734    }
735
736    @Override
737    public void computeScroll() {
738        if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
739        if (!mScroller.isFinished()) {
740            if (mScroller.computeScrollOffset()) {
741                if (DEBUG) Log.i(TAG, "computeScroll: still scrolling");
742                int oldX = getScrollX();
743                int oldY = getScrollY();
744                int x = mScroller.getCurrX();
745                int y = mScroller.getCurrY();
746
747                if (oldX != x || oldY != y) {
748                    scrollTo(x, y);
749                }
750
751                if (mOnPageChangeListener != null) {
752                    final int width = getWidth();
753                    final int position = x / width;
754                    final int offsetPixels = x % width;
755                    final float offset = (float) offsetPixels / width;
756                    mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
757                }
758
759                // Keep on drawing until the animation has finished.
760                invalidate();
761                return;
762            }
763        }
764
765        // Done with scroll, clean up state.
766        completeScroll();
767    }
768
769    private void completeScroll() {
770        boolean needPopulate = mScrolling;
771        if (needPopulate) {
772            // Done with scroll, no longer want to cache view drawing.
773            setScrollingCacheEnabled(false);
774            mScroller.abortAnimation();
775            int oldX = getScrollX();
776            int oldY = getScrollY();
777            int x = mScroller.getCurrX();
778            int y = mScroller.getCurrY();
779            if (oldX != x || oldY != y) {
780                scrollTo(x, y);
781            }
782            setScrollState(SCROLL_STATE_IDLE);
783        }
784        mPopulatePending = false;
785        mScrolling = false;
786        for (int i=0; i<mItems.size(); i++) {
787            ItemInfo ii = mItems.get(i);
788            if (ii.scrolling) {
789                needPopulate = true;
790                ii.scrolling = false;
791            }
792        }
793        if (needPopulate) {
794            populate();
795        }
796    }
797
798    @Override
799    public boolean onInterceptTouchEvent(MotionEvent ev) {
800        /*
801         * This method JUST determines whether we want to intercept the motion.
802         * If we return true, onMotionEvent will be called and we do the actual
803         * scrolling there.
804         */
805
806        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
807
808        // Always take care of the touch gesture being complete.
809        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
810            // Release the drag.
811            if (DEBUG) Log.v(TAG, "Intercept done!");
812            mIsBeingDragged = false;
813            mIsUnableToDrag = false;
814            mActivePointerId = INVALID_POINTER;
815            return false;
816        }
817
818        // Nothing more to do here if we have decided whether or not we
819        // are dragging.
820        if (action != MotionEvent.ACTION_DOWN) {
821            if (mIsBeingDragged) {
822                if (DEBUG) Log.v(TAG, "Intercept returning true!");
823                return true;
824            }
825            if (mIsUnableToDrag) {
826                if (DEBUG) Log.v(TAG, "Intercept returning false!");
827                return false;
828            }
829        }
830
831        switch (action) {
832            case MotionEvent.ACTION_MOVE: {
833                /*
834                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
835                 * whether the user has moved far enough from his original down touch.
836                 */
837
838                /*
839                * Locally do absolute value. mLastMotionY is set to the y value
840                * of the down event.
841                */
842                final int activePointerId = mActivePointerId;
843                if (activePointerId == INVALID_POINTER) {
844                    // If we don't have a valid id, the touch down wasn't on content.
845                    break;
846                }
847
848                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
849                final float x = MotionEventCompat.getX(ev, pointerIndex);
850                final float dx = x - mLastMotionX;
851                final float xDiff = Math.abs(dx);
852                final float y = MotionEventCompat.getY(ev, pointerIndex);
853                final float yDiff = Math.abs(y - mLastMotionY);
854                final int scrollX = getScrollX();
855                final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
856                        scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
857                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
858
859                if (atEdge || canScroll(this, false, (int) dx, (int) x, (int) y)) {
860                    // Nested view has scrollable area under this point. Let it be handled there.
861                    mInitialMotionX = mLastMotionX = x;
862                    mLastMotionY = y;
863                    return false;
864                }
865                if (xDiff > mTouchSlop && xDiff > yDiff) {
866                    if (DEBUG) Log.v(TAG, "Starting drag!");
867                    mIsBeingDragged = true;
868                    setScrollState(SCROLL_STATE_DRAGGING);
869                    mLastMotionX = x;
870                    setScrollingCacheEnabled(true);
871                } else {
872                    if (yDiff > mTouchSlop) {
873                        // The finger has moved enough in the vertical
874                        // direction to be counted as a drag...  abort
875                        // any attempt to drag horizontally, to work correctly
876                        // with children that have scrolling containers.
877                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
878                        mIsUnableToDrag = true;
879                    }
880                }
881                break;
882            }
883
884            case MotionEvent.ACTION_DOWN: {
885                /*
886                 * Remember location of down touch.
887                 * ACTION_DOWN always refers to pointer index 0.
888                 */
889                mLastMotionX = mInitialMotionX = ev.getX();
890                mLastMotionY = ev.getY();
891                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
892
893                if (mScrollState == SCROLL_STATE_SETTLING) {
894                    // Let the user 'catch' the pager as it animates.
895                    mIsBeingDragged = true;
896                    mIsUnableToDrag = false;
897                    setScrollState(SCROLL_STATE_DRAGGING);
898                } else {
899                    completeScroll();
900                    mIsBeingDragged = false;
901                    mIsUnableToDrag = false;
902                }
903
904                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
905                        + " mIsBeingDragged=" + mIsBeingDragged
906                        + "mIsUnableToDrag=" + mIsUnableToDrag);
907                break;
908            }
909
910            case MotionEventCompat.ACTION_POINTER_UP:
911                onSecondaryPointerUp(ev);
912                break;
913        }
914
915        /*
916        * The only time we want to intercept motion events is if we are in the
917        * drag mode.
918        */
919        return mIsBeingDragged;
920    }
921
922    @Override
923    public boolean onTouchEvent(MotionEvent ev) {
924        if (mFakeDragging) {
925            // A fake drag is in progress already, ignore this real one
926            // but still eat the touch events.
927            // (It is likely that the user is multi-touching the screen.)
928            return true;
929        }
930
931        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
932            // Don't handle edge touches immediately -- they may actually belong to one of our
933            // descendants.
934            return false;
935        }
936
937        if (mAdapter == null || mAdapter.getCount() == 0) {
938            // Nothing to present or scroll; nothing to touch.
939            return false;
940        }
941
942        if (mVelocityTracker == null) {
943            mVelocityTracker = VelocityTracker.obtain();
944        }
945        mVelocityTracker.addMovement(ev);
946
947        final int action = ev.getAction();
948
949        switch (action & MotionEventCompat.ACTION_MASK) {
950            case MotionEvent.ACTION_DOWN: {
951                /*
952                 * If being flinged and user touches, stop the fling. isFinished
953                 * will be false if being flinged.
954                 */
955                completeScroll();
956
957                // Remember where the motion event started
958                mLastMotionX = mInitialMotionX = ev.getX();
959                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
960                break;
961            }
962            case MotionEvent.ACTION_MOVE:
963                if (!mIsBeingDragged) {
964                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
965                    final float x = MotionEventCompat.getX(ev, pointerIndex);
966                    final float xDiff = Math.abs(x - mLastMotionX);
967                    final float y = MotionEventCompat.getY(ev, pointerIndex);
968                    final float yDiff = Math.abs(y - mLastMotionY);
969                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
970                    if (xDiff > mTouchSlop && xDiff > yDiff) {
971                        if (DEBUG) Log.v(TAG, "Starting drag!");
972                        mIsBeingDragged = true;
973                        mLastMotionX = x;
974                        setScrollState(SCROLL_STATE_DRAGGING);
975                        setScrollingCacheEnabled(true);
976                    }
977                }
978                if (mIsBeingDragged) {
979                    // Scroll to follow the motion event
980                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
981                            ev, mActivePointerId);
982                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
983                    final float deltaX = mLastMotionX - x;
984                    mLastMotionX = x;
985                    float scrollX = getScrollX() + deltaX;
986                    final int width = getWidth();
987
988                    final float leftBound = Math.max(0, (mCurItem - 1) * width);
989                    final float rightBound =
990                            Math.min(mCurItem + 1, mAdapter.getCount() - 1) * width;
991                    if (scrollX < leftBound) {
992                        scrollX = leftBound;
993                    } else if (scrollX > rightBound) {
994                        scrollX = rightBound;
995                    }
996                    // Don't lose the rounded component
997                    mLastMotionX += scrollX - (int) scrollX;
998                    scrollTo((int) scrollX, getScrollY());
999                    if (mOnPageChangeListener != null) {
1000                        final int position = (int) scrollX / width;
1001                        final int positionOffsetPixels = (int) scrollX % width;
1002                        final float positionOffset = (float) positionOffsetPixels / width;
1003                        mOnPageChangeListener.onPageScrolled(position, positionOffset,
1004                                positionOffsetPixels);
1005                    }
1006                }
1007                break;
1008            case MotionEvent.ACTION_UP:
1009                if (mIsBeingDragged) {
1010                    final VelocityTracker velocityTracker = mVelocityTracker;
1011                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1012                    int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
1013                            velocityTracker, mActivePointerId);
1014                    mPopulatePending = true;
1015                    if ((Math.abs(initialVelocity) > mMinimumVelocity)
1016                            || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
1017                        if (mLastMotionX > mInitialMotionX) {
1018                            setCurrentItemInternal(mCurItem-1, true, true);
1019                        } else {
1020                            setCurrentItemInternal(mCurItem+1, true, true);
1021                        }
1022                    } else {
1023                        setCurrentItemInternal(mCurItem, true, true);
1024                    }
1025
1026                    mActivePointerId = INVALID_POINTER;
1027                    endDrag();
1028                }
1029                break;
1030            case MotionEvent.ACTION_CANCEL:
1031                if (mIsBeingDragged) {
1032                    setCurrentItemInternal(mCurItem, true, true);
1033                    mActivePointerId = INVALID_POINTER;
1034                    endDrag();
1035                }
1036                break;
1037            case MotionEventCompat.ACTION_POINTER_DOWN: {
1038                final int index = MotionEventCompat.getActionIndex(ev);
1039                final float x = MotionEventCompat.getX(ev, index);
1040                mLastMotionX = x;
1041                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1042                break;
1043            }
1044            case MotionEventCompat.ACTION_POINTER_UP:
1045                onSecondaryPointerUp(ev);
1046                mLastMotionX = MotionEventCompat.getX(ev,
1047                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1048                break;
1049        }
1050        return true;
1051    }
1052
1053    /**
1054     * Start a fake drag of the pager.
1055     *
1056     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
1057     * with the touch scrolling of another view, while still letting the ViewPager
1058     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
1059     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
1060     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
1061     *
1062     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
1063     * is already in progress, this method will return false.
1064     *
1065     * @return true if the fake drag began successfully, false if it could not be started.
1066     *
1067     * @see #fakeDragBy(float)
1068     * @see #endFakeDrag()
1069     */
1070    public boolean beginFakeDrag() {
1071        if (mIsBeingDragged) {
1072            return false;
1073        }
1074        mFakeDragging = true;
1075        setScrollState(SCROLL_STATE_DRAGGING);
1076        mInitialMotionX = mLastMotionX = 0;
1077        if (mVelocityTracker == null) {
1078            mVelocityTracker = VelocityTracker.obtain();
1079        } else {
1080            mVelocityTracker.clear();
1081        }
1082        final long time = SystemClock.uptimeMillis();
1083        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
1084        mVelocityTracker.addMovement(ev);
1085        ev.recycle();
1086        mFakeDragBeginTime = time;
1087        return true;
1088    }
1089
1090    /**
1091     * End a fake drag of the pager.
1092     *
1093     * @see #beginFakeDrag()
1094     * @see #fakeDragBy(float)
1095     */
1096    public void endFakeDrag() {
1097        if (!mFakeDragging) {
1098            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1099        }
1100
1101        final VelocityTracker velocityTracker = mVelocityTracker;
1102        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1103        int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
1104                velocityTracker, mActivePointerId);
1105        mPopulatePending = true;
1106        if ((Math.abs(initialVelocity) > mMinimumVelocity)
1107                || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
1108            if (mLastMotionX > mInitialMotionX) {
1109                setCurrentItemInternal(mCurItem-1, true, true);
1110            } else {
1111                setCurrentItemInternal(mCurItem+1, true, true);
1112            }
1113        } else {
1114            setCurrentItemInternal(mCurItem, true, true);
1115        }
1116        endDrag();
1117
1118        mFakeDragging = false;
1119    }
1120
1121    /**
1122     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
1123     *
1124     * @param xOffset Offset in pixels to drag by.
1125     * @see #beginFakeDrag()
1126     * @see #endFakeDrag()
1127     */
1128    public void fakeDragBy(float xOffset) {
1129        if (!mFakeDragging) {
1130            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1131        }
1132
1133        mLastMotionX += xOffset;
1134        float scrollX = getScrollX() - xOffset;
1135        final int width = getWidth();
1136
1137        final float leftBound = Math.max(0, (mCurItem - 1) * width);
1138        final float rightBound =
1139                Math.min(mCurItem + 1, mAdapter.getCount() - 1) * width;
1140        if (scrollX < leftBound) {
1141            scrollX = leftBound;
1142        } else if (scrollX > rightBound) {
1143            scrollX = rightBound;
1144        }
1145        // Don't lose the rounded component
1146        mLastMotionX += scrollX - (int) scrollX;
1147        scrollTo((int) scrollX, getScrollY());
1148        if (mOnPageChangeListener != null) {
1149            final int position = (int) scrollX / width;
1150            final int positionOffsetPixels = (int) scrollX % width;
1151            final float positionOffset = (float) positionOffsetPixels / width;
1152            mOnPageChangeListener.onPageScrolled(position, positionOffset,
1153                    positionOffsetPixels);
1154        }
1155
1156        // Synthesize an event for the VelocityTracker.
1157        final long time = SystemClock.uptimeMillis();
1158        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
1159                mLastMotionX, 0, 0);
1160        mVelocityTracker.addMovement(ev);
1161        ev.recycle();
1162    }
1163
1164    /**
1165     * Returns true if a fake drag is in progress.
1166     *
1167     * @return true if currently in a fake drag, false otherwise.
1168     *
1169     * @see #beginFakeDrag()
1170     * @see #fakeDragBy(float)
1171     * @see #endFakeDrag()
1172     */
1173    public boolean isFakeDragging() {
1174        return mFakeDragging;
1175    }
1176
1177    private void onSecondaryPointerUp(MotionEvent ev) {
1178        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1179        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1180        if (pointerId == mActivePointerId) {
1181            // This was our active pointer going up. Choose a new
1182            // active pointer and adjust accordingly.
1183            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1184            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
1185            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
1186            if (mVelocityTracker != null) {
1187                mVelocityTracker.clear();
1188            }
1189        }
1190    }
1191
1192    private void endDrag() {
1193        mIsBeingDragged = false;
1194        mIsUnableToDrag = false;
1195
1196        if (mVelocityTracker != null) {
1197            mVelocityTracker.recycle();
1198            mVelocityTracker = null;
1199        }
1200    }
1201
1202    private void setScrollingCacheEnabled(boolean enabled) {
1203        if (mScrollingCacheEnabled != enabled) {
1204            mScrollingCacheEnabled = enabled;
1205            if (USE_CACHE) {
1206                final int size = getChildCount();
1207                for (int i = 0; i < size; ++i) {
1208                    final View child = getChildAt(i);
1209                    if (child.getVisibility() != GONE) {
1210                        child.setDrawingCacheEnabled(enabled);
1211                    }
1212                }
1213            }
1214        }
1215    }
1216
1217    /**
1218     * Tests scrollability within child views of v given a delta of dx.
1219     *
1220     * @param v View to test for horizontal scrollability
1221     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1222     *               or just its children (false).
1223     * @param dx Delta scrolled in pixels
1224     * @param x X coordinate of the active touch point
1225     * @param y Y coordinate of the active touch point
1226     * @return true if child views of v can be scrolled by delta of dx.
1227     */
1228    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1229        if (v instanceof ViewGroup) {
1230            final ViewGroup group = (ViewGroup) v;
1231            final int scrollX = v.getScrollX();
1232            final int scrollY = v.getScrollY();
1233            final int count = group.getChildCount();
1234            // Count backwards - let topmost views consume scroll distance first.
1235            for (int i = count - 1; i >= 0; i--) {
1236                // TODO: Add versioned support here for transformed views.
1237                // This will not work for transformed views in Honeycomb+
1238                final View child = group.getChildAt(i);
1239                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1240                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1241                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
1242                                y + scrollY - child.getTop())) {
1243                    return true;
1244                }
1245            }
1246        }
1247
1248        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1249    }
1250
1251    @Override
1252    public boolean dispatchKeyEvent(KeyEvent event) {
1253        // Let the focused view and/or our descendants get the key first
1254        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
1255    }
1256
1257    /**
1258     * You can call this function yourself to have the scroll view perform
1259     * scrolling from a key event, just as if the event had been dispatched to
1260     * it by the view hierarchy.
1261     *
1262     * @param event The key event to execute.
1263     * @return Return true if the event was handled, else false.
1264     */
1265    public boolean executeKeyEvent(KeyEvent event) {
1266        boolean handled = false;
1267        if (event.getAction() == KeyEvent.ACTION_DOWN) {
1268            switch (event.getKeyCode()) {
1269                case KeyEvent.KEYCODE_DPAD_LEFT:
1270                    handled = arrowScroll(FOCUS_LEFT);
1271                    break;
1272                case KeyEvent.KEYCODE_DPAD_RIGHT:
1273                    handled = arrowScroll(FOCUS_RIGHT);
1274                    break;
1275                case KeyEvent.KEYCODE_TAB:
1276                    if (KeyEventCompat.hasNoModifiers(event)) {
1277                        handled = arrowScroll(FOCUS_FORWARD);
1278                    } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
1279                        handled = arrowScroll(FOCUS_BACKWARD);
1280                    }
1281                    break;
1282            }
1283        }
1284        return handled;
1285    }
1286
1287    public boolean arrowScroll(int direction) {
1288        View currentFocused = findFocus();
1289        if (currentFocused == this) currentFocused = null;
1290
1291        boolean handled = false;
1292
1293        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
1294                direction);
1295        if (nextFocused != null && nextFocused != currentFocused) {
1296            if (direction == View.FOCUS_LEFT) {
1297                // If there is nothing to the left, or this is causing us to
1298                // jump to the right, then what we really want to do is page left.
1299                if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
1300                    handled = pageLeft();
1301                } else {
1302                    handled = nextFocused.requestFocus();
1303                }
1304            } else if (direction == View.FOCUS_RIGHT) {
1305                // If there is nothing to the right, or this is causing us to
1306                // jump to the left, then what we really want to do is page right.
1307                if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
1308                    handled = pageRight();
1309                } else {
1310                    handled = nextFocused.requestFocus();
1311                }
1312            }
1313        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
1314            // Trying to move left and nothing there; try to page.
1315            handled = pageLeft();
1316        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
1317            // Trying to move right and nothing there; try to page.
1318            handled = pageRight();
1319        }
1320        if (handled) {
1321            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1322        }
1323        return handled;
1324    }
1325
1326    boolean pageLeft() {
1327        if (mCurItem > 0) {
1328            setCurrentItem(mCurItem-1, true);
1329            return true;
1330        }
1331        return false;
1332    }
1333
1334    boolean pageRight() {
1335        if (mCurItem < (mAdapter.getCount()-1)) {
1336            setCurrentItem(mCurItem+1, true);
1337            return true;
1338        }
1339        return false;
1340    }
1341
1342    /**
1343     * We only want the current page that is being shown to be focusable.
1344     */
1345    @Override
1346    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1347        final int focusableCount = views.size();
1348
1349        final int descendantFocusability = getDescendantFocusability();
1350
1351        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
1352            for (int i = 0; i < getChildCount(); i++) {
1353                final View child = getChildAt(i);
1354                if (child.getVisibility() == VISIBLE) {
1355                    ItemInfo ii = infoForChild(child);
1356                    if (ii != null && ii.position == mCurItem) {
1357                        child.addFocusables(views, direction, focusableMode);
1358                    }
1359                }
1360            }
1361        }
1362
1363        // we add ourselves (if focusable) in all cases except for when we are
1364        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
1365        // to avoid the focus search finding layouts when a more precise search
1366        // among the focusable children would be more interesting.
1367        if (
1368            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
1369                // No focusable descendants
1370                (focusableCount == views.size())) {
1371            // Note that we can't call the superclass here, because it will
1372            // add all views in.  So we need to do the same thing View does.
1373            if (!isFocusable()) {
1374                return;
1375            }
1376            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
1377                    isInTouchMode() && !isFocusableInTouchMode()) {
1378                return;
1379            }
1380            if (views != null) {
1381                views.add(this);
1382            }
1383        }
1384    }
1385
1386    /**
1387     * We only want the current page that is being shown to be touchable.
1388     */
1389    @Override
1390    public void addTouchables(ArrayList<View> views) {
1391        // Note that we don't call super.addTouchables(), which means that
1392        // we don't call View.addTouchables().  This is okay because a ViewPager
1393        // is itself not touchable.
1394        for (int i = 0; i < getChildCount(); i++) {
1395            final View child = getChildAt(i);
1396            if (child.getVisibility() == VISIBLE) {
1397                ItemInfo ii = infoForChild(child);
1398                if (ii != null && ii.position == mCurItem) {
1399                    child.addTouchables(views);
1400                }
1401            }
1402        }
1403    }
1404
1405    /**
1406     * We only want the current page that is being shown to be focusable.
1407     */
1408    @Override
1409    protected boolean onRequestFocusInDescendants(int direction,
1410            Rect previouslyFocusedRect) {
1411        int index;
1412        int increment;
1413        int end;
1414        int count = getChildCount();
1415        if ((direction & FOCUS_FORWARD) != 0) {
1416            index = 0;
1417            increment = 1;
1418            end = count;
1419        } else {
1420            index = count - 1;
1421            increment = -1;
1422            end = -1;
1423        }
1424        for (int i = index; i != end; i += increment) {
1425            View child = getChildAt(i);
1426            if (child.getVisibility() == VISIBLE) {
1427                ItemInfo ii = infoForChild(child);
1428                if (ii != null && ii.position == mCurItem) {
1429                    if (child.requestFocus(direction, previouslyFocusedRect)) {
1430                        return true;
1431                    }
1432                }
1433            }
1434        }
1435        return false;
1436    }
1437
1438    @Override
1439    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
1440        // Scroll to the page that contains the child.
1441        final ItemInfo ii = infoForChild(child);
1442        if (ii != null) {
1443            setCurrentItem(ii.position, !immediate);
1444            return true;
1445        }
1446        return false;
1447    }
1448
1449    @Override
1450    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1451        // ViewPagers should only report accessibility info for the current page,
1452        // otherwise things get very confusing.
1453
1454        // TODO: Should this note something about the paging container?
1455
1456        final int childCount = getChildCount();
1457        for (int i = 0; i < childCount; i++) {
1458            final View child = getChildAt(i);
1459            if (child.getVisibility() == VISIBLE) {
1460                final ItemInfo ii = infoForChild(child);
1461                if (ii != null && ii.position == mCurItem &&
1462                        child.dispatchPopulateAccessibilityEvent(event)) {
1463                    return true;
1464                }
1465            }
1466        }
1467
1468        return false;
1469    }
1470
1471    private class DataSetObserver implements PagerAdapter.DataSetObserver {
1472        @Override
1473        public void onDataSetChanged() {
1474            dataSetChanged();
1475        }
1476    }
1477}
1478