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