ViewPager.java revision 3040c142491acd4a09e7d0add073be0067aec2d5
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            setScrollState(SCROLL_STATE_IDLE);
361            return;
362        }
363
364        setScrollingCacheEnabled(true);
365        mScrolling = true;
366        setScrollState(SCROLL_STATE_SETTLING);
367        mScroller.startScroll(sx, sy, dx, dy);
368        invalidate();
369    }
370
371    void addNewItem(int position, int index) {
372        ItemInfo ii = new ItemInfo();
373        ii.position = position;
374        ii.object = mAdapter.instantiateItem(this, position);
375        if (index < 0) {
376            mItems.add(ii);
377        } else {
378            mItems.add(index, ii);
379        }
380    }
381
382    void dataSetChanged() {
383        // This method only gets called if our observer is attached, so mAdapter is non-null.
384
385        boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
386        int newCurrItem = -1;
387
388        for (int i = 0; i < mItems.size(); i++) {
389            final ItemInfo ii = mItems.get(i);
390            final int newPos = mAdapter.getItemPosition(ii.object);
391
392            if (newPos == PagerAdapter.POSITION_UNCHANGED) {
393                continue;
394            }
395
396            if (newPos == PagerAdapter.POSITION_NONE) {
397                mItems.remove(i);
398                i--;
399                mAdapter.destroyItem(this, ii.position, ii.object);
400                needPopulate = true;
401
402                if (mCurItem == ii.position) {
403                    // Keep the current item in the valid range
404                    newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
405                }
406                continue;
407            }
408
409            if (ii.position != newPos) {
410                if (ii.position == mCurItem) {
411                    // Our current item changed position. Follow it.
412                    newCurrItem = newPos;
413                }
414
415                ii.position = newPos;
416                needPopulate = true;
417            }
418        }
419
420        Collections.sort(mItems, COMPARATOR);
421
422        if (newCurrItem >= 0) {
423            // TODO This currently causes a jump.
424            setCurrentItemInternal(newCurrItem, false, true);
425            needPopulate = true;
426        }
427        if (needPopulate) {
428            populate();
429            requestLayout();
430        }
431    }
432
433    void populate() {
434        if (mAdapter == null) {
435            return;
436        }
437
438        // Bail now if we are waiting to populate.  This is to hold off
439        // on creating views from the time the user releases their finger to
440        // fling to a new position until we have finished the scroll to
441        // that position, avoiding glitches from happening at that point.
442        if (mPopulatePending) {
443            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
444            return;
445        }
446
447        // Also, don't populate until we are attached to a window.  This is to
448        // avoid trying to populate before we have restored our view hierarchy
449        // state and conflicting with what is restored.
450        if (getWindowToken() == null) {
451            return;
452        }
453
454        mAdapter.startUpdate(this);
455
456        final int startPos = mCurItem > 0 ? mCurItem - 1 : mCurItem;
457        final int N = mAdapter.getCount();
458        final int endPos = mCurItem < (N-1) ? mCurItem+1 : N-1;
459
460        if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);
461
462        // Add and remove pages in the existing list.
463        int lastPos = -1;
464        for (int i=0; i<mItems.size(); i++) {
465            ItemInfo ii = mItems.get(i);
466            if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) {
467                if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i);
468                mItems.remove(i);
469                i--;
470                mAdapter.destroyItem(this, ii.position, ii.object);
471            } else if (lastPos < endPos && ii.position > startPos) {
472                // The next item is outside of our range, but we have a gap
473                // between it and the last item where we want to have a page
474                // shown.  Fill in the gap.
475                lastPos++;
476                if (lastPos < startPos) {
477                    lastPos = startPos;
478                }
479                while (lastPos <= endPos && lastPos < ii.position) {
480                    if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i);
481                    addNewItem(lastPos, i);
482                    lastPos++;
483                    i++;
484                }
485            }
486            lastPos = ii.position;
487        }
488
489        // Add any new pages we need at the end.
490        lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1;
491        if (lastPos < endPos) {
492            lastPos++;
493            lastPos = lastPos > startPos ? lastPos : startPos;
494            while (lastPos <= endPos) {
495                if (DEBUG) Log.i(TAG, "appending: " + lastPos);
496                addNewItem(lastPos, -1);
497                lastPos++;
498            }
499        }
500
501        if (DEBUG) {
502            Log.i(TAG, "Current page list:");
503            for (int i=0; i<mItems.size(); i++) {
504                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
505            }
506        }
507
508        ItemInfo curItem = null;
509        for (int i=0; i<mItems.size(); i++) {
510            if (mItems.get(i).position == mCurItem) {
511                curItem = mItems.get(i);
512                break;
513            }
514        }
515        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
516
517        mAdapter.finishUpdate(this);
518
519        if (hasFocus()) {
520            View currentFocused = findFocus();
521            ItemInfo ii = currentFocused != null ? infoForChild(currentFocused) : null;
522            if (ii == null || ii.position != mCurItem) {
523                for (int i=0; i<getChildCount(); i++) {
524                    View child = getChildAt(i);
525                    ii = infoForChild(child);
526                    if (ii != null && ii.position == mCurItem) {
527                        if (child.requestFocus(FOCUS_FORWARD)) {
528                            break;
529                        }
530                    }
531                }
532            }
533        }
534    }
535
536    public static class SavedState extends BaseSavedState {
537        int position;
538        Parcelable adapterState;
539        ClassLoader loader;
540
541        public SavedState(Parcelable superState) {
542            super(superState);
543        }
544
545        @Override
546        public void writeToParcel(Parcel out, int flags) {
547            super.writeToParcel(out, flags);
548            out.writeInt(position);
549            out.writeParcelable(adapterState, flags);
550        }
551
552        @Override
553        public String toString() {
554            return "FragmentPager.SavedState{"
555                    + Integer.toHexString(System.identityHashCode(this))
556                    + " position=" + position + "}";
557        }
558
559        public static final Parcelable.Creator<SavedState> CREATOR
560                = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
561                    @Override
562                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
563                        return new SavedState(in, loader);
564                    }
565                    @Override
566                    public SavedState[] newArray(int size) {
567                        return new SavedState[size];
568                    }
569                });
570
571        SavedState(Parcel in, ClassLoader loader) {
572            super(in);
573            if (loader == null) {
574                loader = getClass().getClassLoader();
575            }
576            position = in.readInt();
577            adapterState = in.readParcelable(loader);
578            this.loader = loader;
579        }
580    }
581
582    @Override
583    public Parcelable onSaveInstanceState() {
584        Parcelable superState = super.onSaveInstanceState();
585        SavedState ss = new SavedState(superState);
586        ss.position = mCurItem;
587        if (mAdapter != null) {
588            ss.adapterState = mAdapter.saveState();
589        }
590        return ss;
591    }
592
593    @Override
594    public void onRestoreInstanceState(Parcelable state) {
595        if (!(state instanceof SavedState)) {
596            super.onRestoreInstanceState(state);
597            return;
598        }
599
600        SavedState ss = (SavedState)state;
601        super.onRestoreInstanceState(ss.getSuperState());
602
603        if (mAdapter != null) {
604            mAdapter.restoreState(ss.adapterState, ss.loader);
605            setCurrentItemInternal(ss.position, false, true);
606        } else {
607            mRestoredCurItem = ss.position;
608            mRestoredAdapterState = ss.adapterState;
609            mRestoredClassLoader = ss.loader;
610        }
611    }
612
613    @Override
614    public void addView(View child, int index, LayoutParams params) {
615        if (mInLayout) {
616            addViewInLayout(child, index, params);
617            child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
618        } else {
619            super.addView(child, index, params);
620        }
621
622        if (USE_CACHE) {
623            if (child.getVisibility() != GONE) {
624                child.setDrawingCacheEnabled(mScrollingCacheEnabled);
625            } else {
626                child.setDrawingCacheEnabled(false);
627            }
628        }
629    }
630
631    ItemInfo infoForChild(View child) {
632        for (int i=0; i<mItems.size(); i++) {
633            ItemInfo ii = mItems.get(i);
634            if (mAdapter.isViewFromObject(child, ii.object)) {
635                return ii;
636            }
637        }
638        return null;
639    }
640
641    @Override
642    protected void onAttachedToWindow() {
643        super.onAttachedToWindow();
644        mFirstLayout = true;
645        if (mAdapter != null) {
646            populate();
647        }
648    }
649
650    @Override
651    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
652        // For simple implementation, or internal size is always 0.
653        // We depend on the container to specify the layout size of
654        // our view.  We can't really know what it is since we will be
655        // adding and removing different arbitrary views and do not
656        // want the layout to change as this happens.
657        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
658                getDefaultSize(0, heightMeasureSpec));
659
660        // Children are just made to fill our space.
661        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
662                getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
663        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
664                getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
665
666        // Make sure we have created all fragments that we need to have shown.
667        mInLayout = true;
668        populate();
669        mInLayout = false;
670
671        // Make sure all children have been properly measured.
672        final int size = getChildCount();
673        for (int i = 0; i < size; ++i) {
674            final View child = getChildAt(i);
675            if (child.getVisibility() != GONE) {
676                if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
677		        + ": " + mChildWidthMeasureSpec);
678                child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
679            }
680        }
681    }
682
683    @Override
684    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
685        super.onSizeChanged(w, h, oldw, oldh);
686
687        // Make sure scroll position is set correctly.
688        if (w != oldw) {
689            if (oldw > 0) {
690                final int oldScrollPos = getScrollX();
691                final int oldScrollItem = oldScrollPos / oldw;
692                final float scrollOffset = (float) (oldScrollPos % oldw) / oldw;
693                final int scrollPos = (int) ((oldScrollItem + scrollOffset) * w);
694                scrollTo(scrollPos, getScrollY());
695                if (!mScroller.isFinished()) {
696                    // We now return to your regularly scheduled scroll, already in progress.
697                    final int newDuration = mScroller.getDuration() - mScroller.timePassed();
698                    mScroller.startScroll(scrollPos, 0, mCurItem * w, 0, newDuration);
699                }
700            } else {
701                int scrollPos = mCurItem * w;
702                if (scrollPos != getScrollX()) {
703                    completeScroll();
704                    scrollTo(scrollPos, getScrollY());
705                }
706            }
707        }
708    }
709
710    @Override
711    protected void onLayout(boolean changed, int l, int t, int r, int b) {
712        mInLayout = true;
713        populate();
714        mInLayout = false;
715
716        final int count = getChildCount();
717        final int width = r-l;
718
719        for (int i = 0; i < count; i++) {
720            View child = getChildAt(i);
721            ItemInfo ii;
722            if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
723                int loff = width*ii.position;
724                int childLeft = getPaddingLeft() + loff;
725                int childTop = getPaddingTop();
726                if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
727		        + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
728		        + "x" + child.getMeasuredHeight());
729                child.layout(childLeft, childTop,
730                        childLeft + child.getMeasuredWidth(),
731                        childTop + child.getMeasuredHeight());
732            }
733        }
734        mFirstLayout = false;
735    }
736
737    @Override
738    public void computeScroll() {
739        if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
740        if (!mScroller.isFinished()) {
741            if (mScroller.computeScrollOffset()) {
742                if (DEBUG) Log.i(TAG, "computeScroll: still scrolling");
743                int oldX = getScrollX();
744                int oldY = getScrollY();
745                int x = mScroller.getCurrX();
746                int y = mScroller.getCurrY();
747
748                if (oldX != x || oldY != y) {
749                    scrollTo(x, y);
750                }
751
752                if (mOnPageChangeListener != null) {
753                    final int width = getWidth();
754                    final int position = x / width;
755                    final int offsetPixels = x % width;
756                    final float offset = (float) offsetPixels / width;
757                    mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
758                }
759
760                // Keep on drawing until the animation has finished.
761                invalidate();
762                return;
763            }
764        }
765
766        // Done with scroll, clean up state.
767        completeScroll();
768    }
769
770    private void completeScroll() {
771        boolean needPopulate = mScrolling;
772        if (needPopulate) {
773            // Done with scroll, no longer want to cache view drawing.
774            setScrollingCacheEnabled(false);
775            mScroller.abortAnimation();
776            int oldX = getScrollX();
777            int oldY = getScrollY();
778            int x = mScroller.getCurrX();
779            int y = mScroller.getCurrY();
780            if (oldX != x || oldY != y) {
781                scrollTo(x, y);
782            }
783            setScrollState(SCROLL_STATE_IDLE);
784        }
785        mPopulatePending = false;
786        mScrolling = false;
787        for (int i=0; i<mItems.size(); i++) {
788            ItemInfo ii = mItems.get(i);
789            if (ii.scrolling) {
790                needPopulate = true;
791                ii.scrolling = false;
792            }
793        }
794        if (needPopulate) {
795            populate();
796        }
797    }
798
799    @Override
800    public boolean onInterceptTouchEvent(MotionEvent ev) {
801        /*
802         * This method JUST determines whether we want to intercept the motion.
803         * If we return true, onMotionEvent will be called and we do the actual
804         * scrolling there.
805         */
806
807        final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
808
809        // Always take care of the touch gesture being complete.
810        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
811            // Release the drag.
812            if (DEBUG) Log.v(TAG, "Intercept done!");
813            mIsBeingDragged = false;
814            mIsUnableToDrag = false;
815            mActivePointerId = INVALID_POINTER;
816            return false;
817        }
818
819        // Nothing more to do here if we have decided whether or not we
820        // are dragging.
821        if (action != MotionEvent.ACTION_DOWN) {
822            if (mIsBeingDragged) {
823                if (DEBUG) Log.v(TAG, "Intercept returning true!");
824                return true;
825            }
826            if (mIsUnableToDrag) {
827                if (DEBUG) Log.v(TAG, "Intercept returning false!");
828                return false;
829            }
830        }
831
832        switch (action) {
833            case MotionEvent.ACTION_MOVE: {
834                /*
835                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
836                 * whether the user has moved far enough from his original down touch.
837                 */
838
839                /*
840                * Locally do absolute value. mLastMotionY is set to the y value
841                * of the down event.
842                */
843                final int activePointerId = mActivePointerId;
844                if (activePointerId == INVALID_POINTER) {
845                    // If we don't have a valid id, the touch down wasn't on content.
846                    break;
847                }
848
849                final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
850                final float x = MotionEventCompat.getX(ev, pointerIndex);
851                final float dx = x - mLastMotionX;
852                final float xDiff = Math.abs(dx);
853                final float y = MotionEventCompat.getY(ev, pointerIndex);
854                final float yDiff = Math.abs(y - mLastMotionY);
855                final int scrollX = getScrollX();
856                final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
857                        scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
858                if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
859
860                if (atEdge || canScroll(this, false, (int) dx, (int) x, (int) y)) {
861                    // Nested view has scrollable area under this point. Let it be handled there.
862                    mInitialMotionX = mLastMotionX = x;
863                    mLastMotionY = y;
864                    return false;
865                }
866                if (xDiff > mTouchSlop && xDiff > yDiff) {
867                    if (DEBUG) Log.v(TAG, "Starting drag!");
868                    mIsBeingDragged = true;
869                    setScrollState(SCROLL_STATE_DRAGGING);
870                    mLastMotionX = x;
871                    setScrollingCacheEnabled(true);
872                } else {
873                    if (yDiff > mTouchSlop) {
874                        // The finger has moved enough in the vertical
875                        // direction to be counted as a drag...  abort
876                        // any attempt to drag horizontally, to work correctly
877                        // with children that have scrolling containers.
878                        if (DEBUG) Log.v(TAG, "Starting unable to drag!");
879                        mIsUnableToDrag = true;
880                    }
881                }
882                break;
883            }
884
885            case MotionEvent.ACTION_DOWN: {
886                /*
887                 * Remember location of down touch.
888                 * ACTION_DOWN always refers to pointer index 0.
889                 */
890                mLastMotionX = mInitialMotionX = ev.getX();
891                mLastMotionY = ev.getY();
892                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
893
894                if (mScrollState == SCROLL_STATE_SETTLING) {
895                    // Let the user 'catch' the pager as it animates.
896                    mIsBeingDragged = true;
897                    mIsUnableToDrag = false;
898                    setScrollState(SCROLL_STATE_DRAGGING);
899                } else {
900                    completeScroll();
901                    mIsBeingDragged = false;
902                    mIsUnableToDrag = false;
903                }
904
905                if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
906                        + " mIsBeingDragged=" + mIsBeingDragged
907                        + "mIsUnableToDrag=" + mIsUnableToDrag);
908                break;
909            }
910
911            case MotionEventCompat.ACTION_POINTER_UP:
912                onSecondaryPointerUp(ev);
913                break;
914        }
915
916        /*
917        * The only time we want to intercept motion events is if we are in the
918        * drag mode.
919        */
920        return mIsBeingDragged;
921    }
922
923    @Override
924    public boolean onTouchEvent(MotionEvent ev) {
925        if (mFakeDragging) {
926            // A fake drag is in progress already, ignore this real one
927            // but still eat the touch events.
928            // (It is likely that the user is multi-touching the screen.)
929            return true;
930        }
931
932        if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
933            // Don't handle edge touches immediately -- they may actually belong to one of our
934            // descendants.
935            return false;
936        }
937
938        if (mAdapter == null || mAdapter.getCount() == 0) {
939            // Nothing to present or scroll; nothing to touch.
940            return false;
941        }
942
943        if (mVelocityTracker == null) {
944            mVelocityTracker = VelocityTracker.obtain();
945        }
946        mVelocityTracker.addMovement(ev);
947
948        final int action = ev.getAction();
949
950        switch (action & MotionEventCompat.ACTION_MASK) {
951            case MotionEvent.ACTION_DOWN: {
952                /*
953                 * If being flinged and user touches, stop the fling. isFinished
954                 * will be false if being flinged.
955                 */
956                completeScroll();
957
958                // Remember where the motion event started
959                mLastMotionX = mInitialMotionX = ev.getX();
960                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
961                break;
962            }
963            case MotionEvent.ACTION_MOVE:
964                if (!mIsBeingDragged) {
965                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
966                    final float x = MotionEventCompat.getX(ev, pointerIndex);
967                    final float xDiff = Math.abs(x - mLastMotionX);
968                    final float y = MotionEventCompat.getY(ev, pointerIndex);
969                    final float yDiff = Math.abs(y - mLastMotionY);
970                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
971                    if (xDiff > mTouchSlop && xDiff > yDiff) {
972                        if (DEBUG) Log.v(TAG, "Starting drag!");
973                        mIsBeingDragged = true;
974                        mLastMotionX = x;
975                        setScrollState(SCROLL_STATE_DRAGGING);
976                        setScrollingCacheEnabled(true);
977                    }
978                }
979                if (mIsBeingDragged) {
980                    // Scroll to follow the motion event
981                    final int activePointerIndex = MotionEventCompat.findPointerIndex(
982                            ev, mActivePointerId);
983                    final float x = MotionEventCompat.getX(ev, activePointerIndex);
984                    final float deltaX = mLastMotionX - x;
985                    mLastMotionX = x;
986                    float scrollX = getScrollX() + deltaX;
987                    final int width = getWidth();
988
989                    final float leftBound = Math.max(0, (mCurItem - 1) * width);
990                    final float rightBound =
991                            Math.min(mCurItem + 1, mAdapter.getCount() - 1) * width;
992                    if (scrollX < leftBound) {
993                        scrollX = leftBound;
994                    } else if (scrollX > rightBound) {
995                        scrollX = rightBound;
996                    }
997                    // Don't lose the rounded component
998                    mLastMotionX += scrollX - (int) scrollX;
999                    scrollTo((int) scrollX, getScrollY());
1000                    if (mOnPageChangeListener != null) {
1001                        final int position = (int) scrollX / width;
1002                        final int positionOffsetPixels = (int) scrollX % width;
1003                        final float positionOffset = (float) positionOffsetPixels / width;
1004                        mOnPageChangeListener.onPageScrolled(position, positionOffset,
1005                                positionOffsetPixels);
1006                    }
1007                }
1008                break;
1009            case MotionEvent.ACTION_UP:
1010                if (mIsBeingDragged) {
1011                    final VelocityTracker velocityTracker = mVelocityTracker;
1012                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1013                    int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
1014                            velocityTracker, mActivePointerId);
1015                    mPopulatePending = true;
1016                    if ((Math.abs(initialVelocity) > mMinimumVelocity)
1017                            || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
1018                        if (mLastMotionX > mInitialMotionX) {
1019                            setCurrentItemInternal(mCurItem-1, true, true);
1020                        } else {
1021                            setCurrentItemInternal(mCurItem+1, true, true);
1022                        }
1023                    } else {
1024                        setCurrentItemInternal(mCurItem, true, true);
1025                    }
1026
1027                    mActivePointerId = INVALID_POINTER;
1028                    endDrag();
1029                }
1030                break;
1031            case MotionEvent.ACTION_CANCEL:
1032                if (mIsBeingDragged) {
1033                    setCurrentItemInternal(mCurItem, true, true);
1034                    mActivePointerId = INVALID_POINTER;
1035                    endDrag();
1036                }
1037                break;
1038            case MotionEventCompat.ACTION_POINTER_DOWN: {
1039                final int index = MotionEventCompat.getActionIndex(ev);
1040                final float x = MotionEventCompat.getX(ev, index);
1041                mLastMotionX = x;
1042                mActivePointerId = MotionEventCompat.getPointerId(ev, index);
1043                break;
1044            }
1045            case MotionEventCompat.ACTION_POINTER_UP:
1046                onSecondaryPointerUp(ev);
1047                mLastMotionX = MotionEventCompat.getX(ev,
1048                        MotionEventCompat.findPointerIndex(ev, mActivePointerId));
1049                break;
1050        }
1051        return true;
1052    }
1053
1054    /**
1055     * Start a fake drag of the pager.
1056     *
1057     * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
1058     * with the touch scrolling of another view, while still letting the ViewPager
1059     * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
1060     * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
1061     * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
1062     *
1063     * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
1064     * is already in progress, this method will return false.
1065     *
1066     * @return true if the fake drag began successfully, false if it could not be started.
1067     *
1068     * @see #fakeDragBy(float)
1069     * @see #endFakeDrag()
1070     */
1071    public boolean beginFakeDrag() {
1072        if (mIsBeingDragged) {
1073            return false;
1074        }
1075        mFakeDragging = true;
1076        setScrollState(SCROLL_STATE_DRAGGING);
1077        mInitialMotionX = mLastMotionX = 0;
1078        if (mVelocityTracker == null) {
1079            mVelocityTracker = VelocityTracker.obtain();
1080        } else {
1081            mVelocityTracker.clear();
1082        }
1083        final long time = SystemClock.uptimeMillis();
1084        final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
1085        mVelocityTracker.addMovement(ev);
1086        ev.recycle();
1087        mFakeDragBeginTime = time;
1088        return true;
1089    }
1090
1091    /**
1092     * End a fake drag of the pager.
1093     *
1094     * @see #beginFakeDrag()
1095     * @see #fakeDragBy(float)
1096     */
1097    public void endFakeDrag() {
1098        if (!mFakeDragging) {
1099            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1100        }
1101
1102        final VelocityTracker velocityTracker = mVelocityTracker;
1103        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1104        int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
1105                velocityTracker, mActivePointerId);
1106        mPopulatePending = true;
1107        if ((Math.abs(initialVelocity) > mMinimumVelocity)
1108                || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
1109            if (mLastMotionX > mInitialMotionX) {
1110                setCurrentItemInternal(mCurItem-1, true, true);
1111            } else {
1112                setCurrentItemInternal(mCurItem+1, true, true);
1113            }
1114        } else {
1115            setCurrentItemInternal(mCurItem, true, true);
1116        }
1117        endDrag();
1118
1119        mFakeDragging = false;
1120    }
1121
1122    /**
1123     * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
1124     *
1125     * @param xOffset Offset in pixels to drag by.
1126     * @see #beginFakeDrag()
1127     * @see #endFakeDrag()
1128     */
1129    public void fakeDragBy(float xOffset) {
1130        if (!mFakeDragging) {
1131            throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
1132        }
1133
1134        mLastMotionX += xOffset;
1135        float scrollX = getScrollX() - xOffset;
1136        final int width = getWidth();
1137
1138        final float leftBound = Math.max(0, (mCurItem - 1) * width);
1139        final float rightBound =
1140                Math.min(mCurItem + 1, mAdapter.getCount() - 1) * width;
1141        if (scrollX < leftBound) {
1142            scrollX = leftBound;
1143        } else if (scrollX > rightBound) {
1144            scrollX = rightBound;
1145        }
1146        // Don't lose the rounded component
1147        mLastMotionX += scrollX - (int) scrollX;
1148        scrollTo((int) scrollX, getScrollY());
1149        if (mOnPageChangeListener != null) {
1150            final int position = (int) scrollX / width;
1151            final int positionOffsetPixels = (int) scrollX % width;
1152            final float positionOffset = (float) positionOffsetPixels / width;
1153            mOnPageChangeListener.onPageScrolled(position, positionOffset,
1154                    positionOffsetPixels);
1155        }
1156
1157        // Synthesize an event for the VelocityTracker.
1158        final long time = SystemClock.uptimeMillis();
1159        final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
1160                mLastMotionX, 0, 0);
1161        mVelocityTracker.addMovement(ev);
1162        ev.recycle();
1163    }
1164
1165    /**
1166     * Returns true if a fake drag is in progress.
1167     *
1168     * @return true if currently in a fake drag, false otherwise.
1169     *
1170     * @see #beginFakeDrag()
1171     * @see #fakeDragBy(float)
1172     * @see #endFakeDrag()
1173     */
1174    public boolean isFakeDragging() {
1175        return mFakeDragging;
1176    }
1177
1178    private void onSecondaryPointerUp(MotionEvent ev) {
1179        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
1180        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
1181        if (pointerId == mActivePointerId) {
1182            // This was our active pointer going up. Choose a new
1183            // active pointer and adjust accordingly.
1184            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1185            mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
1186            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
1187            if (mVelocityTracker != null) {
1188                mVelocityTracker.clear();
1189            }
1190        }
1191    }
1192
1193    private void endDrag() {
1194        mIsBeingDragged = false;
1195        mIsUnableToDrag = false;
1196
1197        if (mVelocityTracker != null) {
1198            mVelocityTracker.recycle();
1199            mVelocityTracker = null;
1200        }
1201    }
1202
1203    private void setScrollingCacheEnabled(boolean enabled) {
1204        if (mScrollingCacheEnabled != enabled) {
1205            mScrollingCacheEnabled = enabled;
1206            if (USE_CACHE) {
1207                final int size = getChildCount();
1208                for (int i = 0; i < size; ++i) {
1209                    final View child = getChildAt(i);
1210                    if (child.getVisibility() != GONE) {
1211                        child.setDrawingCacheEnabled(enabled);
1212                    }
1213                }
1214            }
1215        }
1216    }
1217
1218    /**
1219     * Tests scrollability within child views of v given a delta of dx.
1220     *
1221     * @param v View to test for horizontal scrollability
1222     * @param checkV Whether the view v passed should itself be checked for scrollability (true),
1223     *               or just its children (false).
1224     * @param dx Delta scrolled in pixels
1225     * @param x X coordinate of the active touch point
1226     * @param y Y coordinate of the active touch point
1227     * @return true if child views of v can be scrolled by delta of dx.
1228     */
1229    protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
1230        if (v instanceof ViewGroup) {
1231            final ViewGroup group = (ViewGroup) v;
1232            final int scrollX = v.getScrollX();
1233            final int scrollY = v.getScrollY();
1234            final int count = group.getChildCount();
1235            // Count backwards - let topmost views consume scroll distance first.
1236            for (int i = count - 1; i >= 0; i--) {
1237                // TODO: Add versioned support here for transformed views.
1238                // This will not work for transformed views in Honeycomb+
1239                final View child = group.getChildAt(i);
1240                if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
1241                        y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
1242                        canScroll(child, true, dx, x + scrollX - child.getLeft(),
1243                                y + scrollY - child.getTop())) {
1244                    return true;
1245                }
1246            }
1247        }
1248
1249        return checkV && ViewCompat.canScrollHorizontally(v, -dx);
1250    }
1251
1252    @Override
1253    public boolean dispatchKeyEvent(KeyEvent event) {
1254        // Let the focused view and/or our descendants get the key first
1255        return super.dispatchKeyEvent(event) || executeKeyEvent(event);
1256    }
1257
1258    /**
1259     * You can call this function yourself to have the scroll view perform
1260     * scrolling from a key event, just as if the event had been dispatched to
1261     * it by the view hierarchy.
1262     *
1263     * @param event The key event to execute.
1264     * @return Return true if the event was handled, else false.
1265     */
1266    public boolean executeKeyEvent(KeyEvent event) {
1267        boolean handled = false;
1268        if (event.getAction() == KeyEvent.ACTION_DOWN) {
1269            switch (event.getKeyCode()) {
1270                case KeyEvent.KEYCODE_DPAD_LEFT:
1271                    handled = arrowScroll(FOCUS_LEFT);
1272                    break;
1273                case KeyEvent.KEYCODE_DPAD_RIGHT:
1274                    handled = arrowScroll(FOCUS_RIGHT);
1275                    break;
1276                case KeyEvent.KEYCODE_TAB:
1277                    if (KeyEventCompat.hasNoModifiers(event)) {
1278                        handled = arrowScroll(FOCUS_FORWARD);
1279                    } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
1280                        handled = arrowScroll(FOCUS_BACKWARD);
1281                    }
1282                    break;
1283            }
1284        }
1285        return handled;
1286    }
1287
1288    public boolean arrowScroll(int direction) {
1289        View currentFocused = findFocus();
1290        if (currentFocused == this) currentFocused = null;
1291
1292        boolean handled = false;
1293
1294        View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
1295                direction);
1296        if (nextFocused != null && nextFocused != currentFocused) {
1297            if (direction == View.FOCUS_LEFT) {
1298                // If there is nothing to the left, or this is causing us to
1299                // jump to the right, then what we really want to do is page left.
1300                if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
1301                    handled = pageLeft();
1302                } else {
1303                    handled = nextFocused.requestFocus();
1304                }
1305            } else if (direction == View.FOCUS_RIGHT) {
1306                // If there is nothing to the right, or this is causing us to
1307                // jump to the left, then what we really want to do is page right.
1308                if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
1309                    handled = pageRight();
1310                } else {
1311                    handled = nextFocused.requestFocus();
1312                }
1313            }
1314        } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
1315            // Trying to move left and nothing there; try to page.
1316            handled = pageLeft();
1317        } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
1318            // Trying to move right and nothing there; try to page.
1319            handled = pageRight();
1320        }
1321        if (handled) {
1322            playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
1323        }
1324        return handled;
1325    }
1326
1327    boolean pageLeft() {
1328        if (mCurItem > 0) {
1329            setCurrentItem(mCurItem-1, true);
1330            return true;
1331        }
1332        return false;
1333    }
1334
1335    boolean pageRight() {
1336        if (mCurItem < (mAdapter.getCount()-1)) {
1337            setCurrentItem(mCurItem+1, true);
1338            return true;
1339        }
1340        return false;
1341    }
1342
1343    /**
1344     * We only want the current page that is being shown to be focusable.
1345     */
1346    @Override
1347    public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
1348        final int focusableCount = views.size();
1349
1350        final int descendantFocusability = getDescendantFocusability();
1351
1352        if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
1353            for (int i = 0; i < getChildCount(); i++) {
1354                final View child = getChildAt(i);
1355                if (child.getVisibility() == VISIBLE) {
1356                    ItemInfo ii = infoForChild(child);
1357                    if (ii != null && ii.position == mCurItem) {
1358                        child.addFocusables(views, direction, focusableMode);
1359                    }
1360                }
1361            }
1362        }
1363
1364        // we add ourselves (if focusable) in all cases except for when we are
1365        // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
1366        // to avoid the focus search finding layouts when a more precise search
1367        // among the focusable children would be more interesting.
1368        if (
1369            descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
1370                // No focusable descendants
1371                (focusableCount == views.size())) {
1372            // Note that we can't call the superclass here, because it will
1373            // add all views in.  So we need to do the same thing View does.
1374            if (!isFocusable()) {
1375                return;
1376            }
1377            if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
1378                    isInTouchMode() && !isFocusableInTouchMode()) {
1379                return;
1380            }
1381            if (views != null) {
1382                views.add(this);
1383            }
1384        }
1385    }
1386
1387    /**
1388     * We only want the current page that is being shown to be touchable.
1389     */
1390    @Override
1391    public void addTouchables(ArrayList<View> views) {
1392        // Note that we don't call super.addTouchables(), which means that
1393        // we don't call View.addTouchables().  This is okay because a ViewPager
1394        // is itself not touchable.
1395        for (int i = 0; i < getChildCount(); i++) {
1396            final View child = getChildAt(i);
1397            if (child.getVisibility() == VISIBLE) {
1398                ItemInfo ii = infoForChild(child);
1399                if (ii != null && ii.position == mCurItem) {
1400                    child.addTouchables(views);
1401                }
1402            }
1403        }
1404    }
1405
1406    /**
1407     * We only want the current page that is being shown to be focusable.
1408     */
1409    @Override
1410    protected boolean onRequestFocusInDescendants(int direction,
1411            Rect previouslyFocusedRect) {
1412        int index;
1413        int increment;
1414        int end;
1415        int count = getChildCount();
1416        if ((direction & FOCUS_FORWARD) != 0) {
1417            index = 0;
1418            increment = 1;
1419            end = count;
1420        } else {
1421            index = count - 1;
1422            increment = -1;
1423            end = -1;
1424        }
1425        for (int i = index; i != end; i += increment) {
1426            View child = getChildAt(i);
1427            if (child.getVisibility() == VISIBLE) {
1428                ItemInfo ii = infoForChild(child);
1429                if (ii != null && ii.position == mCurItem) {
1430                    if (child.requestFocus(direction, previouslyFocusedRect)) {
1431                        return true;
1432                    }
1433                }
1434            }
1435        }
1436        return false;
1437    }
1438
1439    @Override
1440    public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
1441        // Scroll to the page that contains the child.
1442        final ItemInfo ii = infoForChild(child);
1443        if (ii != null) {
1444            setCurrentItem(ii.position, !immediate);
1445            return true;
1446        }
1447        return false;
1448    }
1449
1450    @Override
1451    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
1452        // ViewPagers should only report accessibility info for the current page,
1453        // otherwise things get very confusing.
1454
1455        // TODO: Should this note something about the paging container?
1456
1457        final int childCount = getChildCount();
1458        for (int i = 0; i < childCount; i++) {
1459            final View child = getChildAt(i);
1460            if (child.getVisibility() == VISIBLE) {
1461                final ItemInfo ii = infoForChild(child);
1462                if (ii != null && ii.position == mCurItem &&
1463                        child.dispatchPopulateAccessibilityEvent(event)) {
1464                    return true;
1465                }
1466            }
1467        }
1468
1469        return false;
1470    }
1471
1472    private class DataSetObserver implements PagerAdapter.DataSetObserver {
1473        @Override
1474        public void onDataSetChanged() {
1475            dataSetChanged();
1476        }
1477    }
1478}
1479