zoukankan      html  css  js  c++  java
  • Android优化之ViewPager的懒加载

    转载本博客请注明出处:点击打开链接    http://blog.csdn.net/qq_32059827/article/details/52487794

    出于对用户消耗流量的考虑,有必要对viewpage做懒加载。对于什么是懒加载,首先应该理解什么是预加载:

    对于ViewPage默认是加载三个pages,即用户选择当前界面的时候,该page两边的pages都已经初始化完毕(数据、界面、网络等)。这就是预加载机制。对于ViewPage的使用详解,请看之前的博客,地址:点击打开链接  http://blog.csdn.net/qq_32059827/article/details/52300291。

    出于对上篇的完善,看一下viewpage加载部分的源代码是怎么样的:

                    private static final int DEFAULT_OFFSCREEN_PAGES = 1;
    		mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
    
    	       public void setOffscreenPageLimit(int limit) {
    	         if (limit < DEFAULT_OFFSCREEN_PAGES) {
    	            Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
    	                    DEFAULT_OFFSCREEN_PAGES);
    	            limit = DEFAULT_OFFSCREEN_PAGES;
    	        }
    	        if (limit != mOffscreenPageLimit) {
    	            mOffscreenPageLimit = limit;
    	            populate();
    	        }
        	}

    可见,系统默认值为1,这个值就是当前page左右应该加载几张page。那么在这种默认情况下,我们想要是limit =0;通过代码可以看到,会报一个警告:TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +  DEFAULT_OFFSCREEN_PAGES。 

    而且还是把1传给了limit。

    了解了预加载,再说一下懒加载。懒加载就是屏蔽viewpage默认加载加载机制,说白了,就是强制设置上边的limit值 等于0.

    那么,系统不让设置,应该怎么办呢?

    其实在高版本的V4包里面,已经没有什么办法修改这个值了。还好,使用低版本的V4包里面的ViewPage类,还是可以改动的。那么,这样就建立一个自己的ViewPage。

    方法:

         1、新建自己的类MyViewPage  extends ViewPage      (注意:这个ViewPage低版本)

         2、修改如下代码:private static final int DEFAULT_OFFSCREEN_PAGES = 0;     //默认的加载页面,ViewPager是1个

         3、布局文件修改:

           <com.example.smartsbj.view.MyViewPage
            android:id="@+id/vp_mainmenu_view_pages"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1" >
        </com.example.smartsbj.view.MyViewPage>

    好了,上边已经解决了问题。

    如果还是不好操作,闲麻烦的话,那么就把下边这1700行代码直接法制到您的MyViewPage类里面去吧,直接使用就好了。

    代码有点长,不过您只要会Ctrl+C,Ctrl+V就好了:


    package com.example.itydl.view;
    
    /*
     * Copyright (C) 2011 The Android Open Source Project
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    
    import android.content.Context;
    import android.database.DataSetObserver;
    import android.graphics.Canvas;
    import android.graphics.Rect;
    import android.graphics.drawable.Drawable;
    import android.os.Parcel;
    import android.os.Parcelable;
    import android.os.SystemClock;
    import android.support.v4.os.ParcelableCompat;
    import android.support.v4.os.ParcelableCompatCreatorCallbacks;
    import android.support.v4.view.KeyEventCompat;
    import android.support.v4.view.MotionEventCompat;
    import android.support.v4.view.PagerAdapter;
    import android.support.v4.view.VelocityTrackerCompat;
    import android.support.v4.view.ViewCompat;
    import android.support.v4.view.ViewConfigurationCompat;
    import android.support.v4.widget.EdgeEffectCompat;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.FocusFinder;
    import android.view.KeyEvent;
    import android.view.MotionEvent;
    import android.view.SoundEffectConstants;
    import android.view.VelocityTracker;
    import android.view.View;
    import android.view.ViewConfiguration;
    import android.view.ViewGroup;
    import android.view.ViewParent;
    import android.view.accessibility.AccessibilityEvent;
    import android.view.animation.Interpolator;
    import android.widget.Scroller;
    
    
    public class MyViewPage extends ViewGroup {
    
    	private static final String TAG = "LazyViewPager";
        private static final boolean DEBUG = false;
    
        private static final boolean USE_CACHE = false;
    
        private static final int DEFAULT_OFFSCREEN_PAGES = 0; 	//默认的加载页面,ViewPager是1个,所以会加载两个Fragment
        private static final int MAX_SETTLE_DURATION = 600;
    
        static class ItemInfo {
            Object object;
            int position;
            boolean scrolling;
        }
    
        private static final Comparator<ItemInfo> COMPARATOR = new Comparator<ItemInfo>(){
            @Override
            public int compare(ItemInfo lhs, ItemInfo rhs) {
                return lhs.position - rhs.position;
            }};
    
        private static final Interpolator sInterpolator = new Interpolator() {
            public float getInterpolation(float t) {
                // _o(t) = t * t * ((tension + 1) * t + tension)
                // o(t) = _o(t - 1) + 1
                t -= 1.0f;
                return t * t * t + 1.0f;
            }
        };
    
        private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
    
        private PagerAdapter mAdapter;
        private int mCurItem;   // Index of currently displayed page.
        private int mRestoredCurItem = -1;
        private Parcelable mRestoredAdapterState = null;
        private ClassLoader mRestoredClassLoader = null;
        private Scroller mScroller;
        private PagerObserver mObserver;
    
        private int mPageMargin;
        private Drawable mMarginDrawable;
    
        private int mChildWidthMeasureSpec;
        private int mChildHeightMeasureSpec;
        private boolean mInLayout;
    
        private boolean mScrollingCacheEnabled;
    
        private boolean mPopulatePending;
        private boolean mScrolling;
        private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
    
        private boolean mIsBeingDragged;
        private boolean mIsUnableToDrag;
        private int mTouchSlop;
        private float mInitialMotionX;
        /**
         * Position of the last motion event.
         */
        private float mLastMotionX;
        private float mLastMotionY;
        /**
         * ID of the active pointer. This is used to retain consistency during
         * drags/flings if multiple pointers are used.
         */
        private int mActivePointerId = INVALID_POINTER;
        /**
         * Sentinel value for no current active pointer.
         * Used by {@link #mActivePointerId}.
         */
        private static final int INVALID_POINTER = -1;
    
        /**
         * Determines speed during touch scrolling
         */
        private VelocityTracker mVelocityTracker;
        private int mMinimumVelocity;
        private int mMaximumVelocity;
        private float mBaseLineFlingVelocity;
        private float mFlingVelocityInfluence;
    
        private boolean mFakeDragging;
        private long mFakeDragBeginTime;
    
        private EdgeEffectCompat mLeftEdge;
        private EdgeEffectCompat mRightEdge;
    
        private boolean mFirstLayout = true;
    
        private OnPageChangeListener mOnPageChangeListener;
    
        /**
         * Indicates that the pager is in an idle, settled state. The current page
         * is fully in view and no animation is in progress.
         */
        public static final int SCROLL_STATE_IDLE = 0;
    
        /**
         * Indicates that the pager is currently being dragged by the user.
         */
        public static final int SCROLL_STATE_DRAGGING = 1;
    
        /**
         * Indicates that the pager is in the process of settling to a final position.
         */
        public static final int SCROLL_STATE_SETTLING = 2;
    
        private int mScrollState = SCROLL_STATE_IDLE;
    
        /**
         * Callback interface for responding to changing state of the selected page.
         */
        public interface OnPageChangeListener {
    
            /**
             * This method will be invoked when the current page is scrolled, either as part
             * of a programmatically initiated smooth scroll or a user initiated touch scroll.
             *
             * @param position Position index of the first page currently being displayed.
             *                 Page position+1 will be visible if positionOffset is nonzero.
             * @param positionOffset Value from [0, 1) indicating the offset from the page at position.
             * @param positionOffsetPixels Value in pixels indicating the offset from position.
             */
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels);
    
            /**
             * This method will be invoked when a new page becomes selected. Animation is not
             * necessarily complete.
             *
             * @param position Position index of the new selected page.
             */
            public void onPageSelected(int position);
    
            /**
             * Called when the scroll state changes. Useful for discovering when the user
             * begins dragging, when the pager is automatically settling to the current page,
             * or when it is fully stopped/idle.
             *
             * @param state The new scroll state.
             * @see android.support.v4.view.ViewPager#SCROLL_STATE_IDLE
             * @see android.support.v4.view.ViewPager#SCROLL_STATE_DRAGGING
             * @see android.support.v4.view.ViewPager#SCROLL_STATE_SETTLING
             */
            public void onPageScrollStateChanged(int state);
        }
    
        /**
         * Simple implementation of the {@link android.support.v4.view.LazyViewPager.OnPageChangeListener} interface with stub
         * implementations of each method. Extend this if you do not intend to override
         * every method of {@link android.support.v4.view.LazyViewPager.OnPageChangeListener}.
         */
        public static class SimpleOnPageChangeListener implements OnPageChangeListener {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                // This space for rent
            }
    
            @Override
            public void onPageSelected(int position) {
                // This space for rent
            }
    
            @Override
            public void onPageScrollStateChanged(int state) {
                // This space for rent
            }
        }
    
        public MyViewPage(Context context) {
            super(context);
            initViewPager();
        }
    
        public MyViewPage(Context context, AttributeSet attrs) {
            super(context, attrs);
            initViewPager();
        }
    
        void initViewPager() {
            setWillNotDraw(false);
            setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
            setFocusable(true);
            final Context context = getContext();
            mScroller = new Scroller(context, sInterpolator);
            final ViewConfiguration configuration = ViewConfiguration.get(context);
            mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
            mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
            mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
            mLeftEdge = new EdgeEffectCompat(context);
            mRightEdge = new EdgeEffectCompat(context);
    
            float density = context.getResources().getDisplayMetrics().density;
            mBaseLineFlingVelocity = 2500.0f * density;
            mFlingVelocityInfluence = 0.4f;
        }
    
        private void setScrollState(int newState) {
            if (mScrollState == newState) {
                return;
            }
    
            mScrollState = newState;
            if (mOnPageChangeListener != null) {
                mOnPageChangeListener.onPageScrollStateChanged(newState);
            }
        }
    
        public void setAdapter(PagerAdapter adapter) {
            if (mAdapter != null) {
    //            mAdapter.unregisterDataSetObserver(mObserver);
                mAdapter.startUpdate(this);
                for (int i = 0; i < mItems.size(); i++) {
                    final ItemInfo ii = mItems.get(i);
                    mAdapter.destroyItem(this, ii.position, ii.object);
                }
                mAdapter.finishUpdate(this);
                mItems.clear();
                removeAllViews();
                mCurItem = 0;
                scrollTo(0, 0);
            }
    
            mAdapter = adapter;
    
            if (mAdapter != null) {
                if (mObserver == null) {
                    mObserver = new PagerObserver();
                }
    //            mAdapter.registerDataSetObserver(mObserver);
                mPopulatePending = false;
                if (mRestoredCurItem >= 0) {
                    mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
                    setCurrentItemInternal(mRestoredCurItem, false, true);
                    mRestoredCurItem = -1;
                    mRestoredAdapterState = null;
                    mRestoredClassLoader = null;
                } else {
                    populate();
                }
            }
        }
    
        public PagerAdapter getAdapter() {
            return mAdapter;
        }
    
        /**
         * Set the currently selected page. If the ViewPager has already been through its first
         * layout there will be a smooth animated transition between the current item and the
         * specified item.
         *
         * @param item Item index to select
         */
        public void setCurrentItem(int item) {
            mPopulatePending = false;
            setCurrentItemInternal(item, !mFirstLayout, false);
        }
    
        /**
         * Set the currently selected page.
         *
         * @param item Item index to select
         * @param smoothScroll True to smoothly scroll to the new item, false to transition immediately
         */
        public void setCurrentItem(int item, boolean smoothScroll) {
            mPopulatePending = false;
            setCurrentItemInternal(item, smoothScroll, false);
        }
    
        public int getCurrentItem() {
            return mCurItem;
        }
    
        void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
            setCurrentItemInternal(item, smoothScroll, always, 0);
        }
    
        void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {
            if (mAdapter == null || mAdapter.getCount() <= 0) {
                setScrollingCacheEnabled(false);
                return;
            }
            if (!always && mCurItem == item && mItems.size() != 0) {
                setScrollingCacheEnabled(false);
                return;
            }
            if (item < 0) {
                item = 0;
            } else if (item >= mAdapter.getCount()) {
                item = mAdapter.getCount() - 1;
            }
            final int pageLimit = mOffscreenPageLimit;
            if (item > (mCurItem + pageLimit) || item < (mCurItem - pageLimit)) {
                // We are doing a jump by more than one page.  To avoid
                // glitches, we want to keep all current pages in the view
                // until the scroll ends.
                for (int i=0; i<mItems.size(); i++) {
                    mItems.get(i).scrolling = true;
                }
            }
    
            final boolean dispatchSelected = mCurItem != item;
            mCurItem = item;
            populate();
            final int destX = (getWidth() + mPageMargin) * item;
            if (smoothScroll) {
                smoothScrollTo(destX, 0, velocity);
                if (dispatchSelected && mOnPageChangeListener != null) {
                    mOnPageChangeListener.onPageSelected(item);
                }
            } else {
                if (dispatchSelected && mOnPageChangeListener != null) {
                    mOnPageChangeListener.onPageSelected(item);
                }
                completeScroll();
                scrollTo(destX, 0);
            }
        }
    
        public void setOnPageChangeListener(OnPageChangeListener listener) {
            mOnPageChangeListener = listener;
        }
    
        /**
         * Returns the number of pages that will be retained to either side of the
         * current page in the view hierarchy in an idle state. Defaults to 1.
         *
         * @return How many pages will be kept offscreen on either side
         * @see #setOffscreenPageLimit(int)
         */
        public int getOffscreenPageLimit() {
            return mOffscreenPageLimit;
        }
    
        /**
         * Set the number of pages that should be retained to either side of the
         * current page in the view hierarchy in an idle state. Pages beyond this
         * limit will be recreated from the adapter when needed.
         *
         * <p>This is offered as an optimization. If you know in advance the number
         * of pages you will need to support or have lazy-loading mechanisms in place
         * on your pages, tweaking this setting can have benefits in perceived smoothness
         * of paging animations and interaction. If you have a small number of pages (3-4)
         * that you can keep active all at once, less time will be spent in layout for
         * newly created view subtrees as the user pages back and forth.</p>
         *
         * <p>You should keep this limit low, especially if your pages have complex layouts.
         * This setting defaults to 1.</p>
         *
         * @param limit How many pages will be kept offscreen in an idle state.
         */
        public void setOffscreenPageLimit(int limit) {
            if (limit < DEFAULT_OFFSCREEN_PAGES) {
                Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
                        DEFAULT_OFFSCREEN_PAGES);
                limit = DEFAULT_OFFSCREEN_PAGES;
            }
            if (limit != mOffscreenPageLimit) {
                mOffscreenPageLimit = limit;
                populate();
            }
        }
    
        /**
         * Set the margin between pages.
         *
         * @param marginPixels Distance between adjacent pages in pixels
         * @see #getPageMargin()
         * @see #setPageMarginDrawable(android.graphics.drawable.Drawable)
         * @see #setPageMarginDrawable(int)
         */
        public void setPageMargin(int marginPixels) {
            final int oldMargin = mPageMargin;
            mPageMargin = marginPixels;
    
            final int width = getWidth();
            recomputeScrollPosition(width, width, marginPixels, oldMargin);
    
            requestLayout();
        }
    
        /**
         * Return the margin between pages.
         *
         * @return The size of the margin in pixels
         */
        public int getPageMargin() {
            return mPageMargin;
        }
    
        /**
         * Set a drawable that will be used to fill the margin between pages.
         *
         * @param d Drawable to display between pages
         */
        public void setPageMarginDrawable(Drawable d) {
            mMarginDrawable = d;
            if (d != null) refreshDrawableState();
            setWillNotDraw(d == null);
            invalidate();
        }
    
        /**
         * Set a drawable that will be used to fill the margin between pages.
         *
         * @param resId Resource ID of a drawable to display between pages
         */
        public void setPageMarginDrawable(int resId) {
            setPageMarginDrawable(getContext().getResources().getDrawable(resId));
        }
    
        @Override
        protected boolean verifyDrawable(Drawable who) {
            return super.verifyDrawable(who) || who == mMarginDrawable;
        }
    
        @Override
        protected void drawableStateChanged() {
            super.drawableStateChanged();
            final Drawable d = mMarginDrawable;
            if (d != null && d.isStateful()) {
                d.setState(getDrawableState());
            }
        }
    
        // We want the duration of the page snap animation to be influenced by the distance that
        // the screen has to travel, however, we don't want this duration to be effected in a
        // purely linear fashion. Instead, we use this method to moderate the effect that the distance
        // of travel has on the overall snap duration.
        float distanceInfluenceForSnapDuration(float f) {
            f -= 0.5f; // center the values about 0.
            f *= 0.3f * Math.PI / 2.0f;
            return (float) Math.sin(f);
        }
    
        /**
         * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
         *
         * @param x the number of pixels to scroll by on the X axis
         * @param y the number of pixels to scroll by on the Y axis
         */
        void smoothScrollTo(int x, int y) {
            smoothScrollTo(x, y, 0);
        }
    
        /**
         * Like {@link android.view.View#scrollBy}, but scroll smoothly instead of immediately.
         *
         * @param x the number of pixels to scroll by on the X axis
         * @param y the number of pixels to scroll by on the Y axis
         * @param velocity the velocity associated with a fling, if applicable. (0 otherwise)
         */
        void smoothScrollTo(int x, int y, int velocity) {
            if (getChildCount() == 0) {
                // Nothing to do.
                setScrollingCacheEnabled(false);
                return;
            }
            int sx = getScrollX();
            int sy = getScrollY();
            int dx = x - sx;
            int dy = y - sy;
            if (dx == 0 && dy == 0) {
                completeScroll();
                setScrollState(SCROLL_STATE_IDLE);
                return;
            }
    
            setScrollingCacheEnabled(true);
            mScrolling = true;
            setScrollState(SCROLL_STATE_SETTLING);
    
            final float pageDelta = (float) Math.abs(dx) / (getWidth() + mPageMargin);
            int duration = (int) (pageDelta * 100);
    
            velocity = Math.abs(velocity);
            if (velocity > 0) {
                duration += (duration / (velocity / mBaseLineFlingVelocity)) * mFlingVelocityInfluence;
            } else {
                duration += 100;
            }
            duration = Math.min(duration, MAX_SETTLE_DURATION);
    
            mScroller.startScroll(sx, sy, dx, dy, duration);
            invalidate();
        }
    
        void addNewItem(int position, int index) {
            ItemInfo ii = new ItemInfo();
            ii.position = position;
            ii.object = mAdapter.instantiateItem(this, position);
            if (index < 0) {
                mItems.add(ii);
            } else {
                mItems.add(index, ii);
            }
        }
    
        void dataSetChanged() {
            // This method only gets called if our observer is attached, so mAdapter is non-null.
    
            boolean needPopulate = mItems.size() < 3 && mItems.size() < mAdapter.getCount();
            int newCurrItem = -1;
    
            for (int i = 0; i < mItems.size(); i++) {
                final ItemInfo ii = mItems.get(i);
                final int newPos = mAdapter.getItemPosition(ii.object);
    
                if (newPos == PagerAdapter.POSITION_UNCHANGED) {
                    continue;
                }
    
                if (newPos == PagerAdapter.POSITION_NONE) {
                    mItems.remove(i);
                    i--;
                    mAdapter.destroyItem(this, ii.position, ii.object);
                    needPopulate = true;
    
                    if (mCurItem == ii.position) {
                        // Keep the current item in the valid range
                        newCurrItem = Math.max(0, Math.min(mCurItem, mAdapter.getCount() - 1));
                    }
                    continue;
                }
    
                if (ii.position != newPos) {
                    if (ii.position == mCurItem) {
                        // Our current item changed position. Follow it.
                        newCurrItem = newPos;
                    }
    
                    ii.position = newPos;
                    needPopulate = true;
                }
            }
    
            Collections.sort(mItems, COMPARATOR);
    
            if (newCurrItem >= 0) {
                // TODO This currently causes a jump.
                setCurrentItemInternal(newCurrItem, false, true);
                needPopulate = true;
            }
            if (needPopulate) {
                populate();
                requestLayout();
            }
        }
    
        void populate() {
            if (mAdapter == null) {
                return;
            }
    
            // Bail now if we are waiting to populate.  This is to hold off
            // on creating views from the time the user releases their finger to
            // fling to a new position until we have finished the scroll to
            // that position, avoiding glitches from happening at that point.
            if (mPopulatePending) {
                if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
                return;
            }
    
            // Also, don't populate until we are attached to a window.  This is to
            // avoid trying to populate before we have restored our view hierarchy
            // state and conflicting with what is restored.
            if (getWindowToken() == null) {
                return;
            }
    
            mAdapter.startUpdate(this);
    
            final int pageLimit = mOffscreenPageLimit;
            
            final int startPos = Math.max(0, mCurItem - pageLimit);
            final int N = mAdapter.getCount();
            final int endPos = Math.min(N-1, mCurItem + pageLimit);
    
            if (DEBUG) Log.v(TAG, "populating: startPos=" + startPos + " endPos=" + endPos);
    
            // Add and remove pages in the existing list.
            int lastPos = -1;
            for (int i=0; i<mItems.size(); i++) {
                ItemInfo ii = mItems.get(i);
                if ((ii.position < startPos || ii.position > endPos) && !ii.scrolling) {
                    if (DEBUG) Log.i(TAG, "removing: " + ii.position + " @ " + i);
                    mItems.remove(i);
                    i--;
                    mAdapter.destroyItem(this, ii.position, ii.object);
                } else if (lastPos < endPos && ii.position > startPos) {
                    // The next item is outside of our range, but we have a gap
                    // between it and the last item where we want to have a page
                    // shown.  Fill in the gap.
                    lastPos++;
                    if (lastPos < startPos) {
                        lastPos = startPos;
                    }
                    while (lastPos <= endPos && lastPos < ii.position) {
                        if (DEBUG) Log.i(TAG, "inserting: " + lastPos + " @ " + i);
                        addNewItem(lastPos, i);
                        lastPos++;
                        i++;
                    }
                }
                lastPos = ii.position;
            }
    
            // Add any new pages we need at the end.
            lastPos = mItems.size() > 0 ? mItems.get(mItems.size()-1).position : -1;
            if (lastPos < endPos) {
                lastPos++;
                lastPos = lastPos > startPos ? lastPos : startPos;
                while (lastPos <= endPos) {
                    if (DEBUG) Log.i(TAG, "appending: " + lastPos);
                    addNewItem(lastPos, -1);
                    lastPos++;
                }
            }
    
            if (DEBUG) {
                Log.i(TAG, "Current page list:");
                for (int i=0; i<mItems.size(); i++) {
                    Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
                }
            }
    
            ItemInfo curItem = null;
            for (int i=0; i<mItems.size(); i++) {
                if (mItems.get(i).position == mCurItem) {
                    curItem = mItems.get(i);
                    break;
                }
            }
            mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
    
            mAdapter.finishUpdate(this);
    
            if (hasFocus()) {
                View currentFocused = findFocus();
                ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
                if (ii == null || ii.position != mCurItem) {
                    for (int i=0; i<getChildCount(); i++) {
                        View child = getChildAt(i);
                        ii = infoForChild(child);
                        if (ii != null && ii.position == mCurItem) {
                            if (child.requestFocus(FOCUS_FORWARD)) {
                                break;
                            }
                        }
                    }
                }
            }
        }
    
        public static class SavedState extends BaseSavedState {
            int position;
            Parcelable adapterState;
            ClassLoader loader;
    
            public SavedState(Parcelable superState) {
                super(superState);
            }
    
            @Override
            public void writeToParcel(Parcel out, int flags) {
                super.writeToParcel(out, flags);
                out.writeInt(position);
                out.writeParcelable(adapterState, flags);
            }
    
            @Override
            public String toString() {
                return "FragmentPager.SavedState{"
                        + Integer.toHexString(System.identityHashCode(this))
                        + " position=" + position + "}";
            }
    
            public static final Creator<SavedState> CREATOR
                    = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<SavedState>() {
                        @Override
                        public SavedState createFromParcel(Parcel in, ClassLoader loader) {
                            return new SavedState(in, loader);
                        }
                        @Override
                        public SavedState[] newArray(int size) {
                            return new SavedState[size];
                        }
                    });
    
            SavedState(Parcel in, ClassLoader loader) {
                super(in);
                if (loader == null) {
                    loader = getClass().getClassLoader();
                }
                position = in.readInt();
                adapterState = in.readParcelable(loader);
                this.loader = loader;
            }
        }
    
        @Override
        public Parcelable onSaveInstanceState() {
            Parcelable superState = super.onSaveInstanceState();
            SavedState ss = new SavedState(superState);
            ss.position = mCurItem;
            if (mAdapter != null) {
                ss.adapterState = mAdapter.saveState();
            }
            return ss;
        }
    
        @Override
        public void onRestoreInstanceState(Parcelable state) {
            if (!(state instanceof SavedState)) {
                super.onRestoreInstanceState(state);
                return;
            }
    
            SavedState ss = (SavedState)state;
            super.onRestoreInstanceState(ss.getSuperState());
    
            if (mAdapter != null) {
                mAdapter.restoreState(ss.adapterState, ss.loader);
                setCurrentItemInternal(ss.position, false, true);
            } else {
                mRestoredCurItem = ss.position;
                mRestoredAdapterState = ss.adapterState;
                mRestoredClassLoader = ss.loader;
            }
        }
    
        @Override
        public void addView(View child, int index, LayoutParams params) {
            if (mInLayout) {
                addViewInLayout(child, index, params);
                child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
            } else {
                super.addView(child, index, params);
            }
    
            if (USE_CACHE) {
                if (child.getVisibility() != GONE) {
                    child.setDrawingCacheEnabled(mScrollingCacheEnabled);
                } else {
                    child.setDrawingCacheEnabled(false);
                }
            }
        }
    
        ItemInfo infoForChild(View child) {
            for (int i=0; i<mItems.size(); i++) {
                ItemInfo ii = mItems.get(i);
                if (mAdapter.isViewFromObject(child, ii.object)) {
                    return ii;
                }
            }
            return null;
        }
    
        ItemInfo infoForAnyChild(View child) {
            ViewParent parent;
            while ((parent=child.getParent()) != this) {
                if (parent == null || !(parent instanceof View)) {
                    return null;
                }
                child = (View)parent;
            }
            return infoForChild(child);
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            mFirstLayout = true;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // For simple implementation, or internal size is always 0.
            // We depend on the container to specify the layout size of
            // our view.  We can't really know what it is since we will be
            // adding and removing different arbitrary views and do not
            // want the layout to change as this happens.
            setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                    getDefaultSize(0, heightMeasureSpec));
    
            // Children are just made to fill our space.
            mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
                    getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);
            mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
                    getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);
    
            // Make sure we have created all fragments that we need to have shown.
            mInLayout = true;
            populate();
            mInLayout = false;
    
            // Make sure all children have been properly measured.
            final int size = getChildCount();
            for (int i = 0; i < size; ++i) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    if (DEBUG) Log.v(TAG, "Measuring #" + i + " " + child
    		        + ": " + mChildWidthMeasureSpec);
                    child.measure(mChildWidthMeasureSpec, mChildHeightMeasureSpec);
                }
            }
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
    
            // Make sure scroll position is set correctly.
            if (w != oldw) {
                recomputeScrollPosition(w, oldw, mPageMargin, mPageMargin);
            }
        }
    
        private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
            final int widthWithMargin = width + margin;
            if (oldWidth > 0) {
                final int oldScrollPos = getScrollX();
                final int oldwwm = oldWidth + oldMargin;
                final int oldScrollItem = oldScrollPos / oldwwm;
                final float scrollOffset = (float) (oldScrollPos % oldwwm) / oldwwm;
                final int scrollPos = (int) ((oldScrollItem + scrollOffset) * widthWithMargin);
                scrollTo(scrollPos, getScrollY());
                if (!mScroller.isFinished()) {
                    // We now return to your regularly scheduled scroll, already in progress.
                    final int newDuration = mScroller.getDuration() - mScroller.timePassed();
                    mScroller.startScroll(scrollPos, 0, mCurItem * widthWithMargin, 0, newDuration);
                }
            } else {
                int scrollPos = mCurItem * widthWithMargin;
                if (scrollPos != getScrollX()) {
                    completeScroll();
                    scrollTo(scrollPos, getScrollY());
                }
            }
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            mInLayout = true;
            populate();
            mInLayout = false;
    
            final int count = getChildCount();
            final int width = r-l;
    
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                ItemInfo ii;
                if (child.getVisibility() != GONE && (ii=infoForChild(child)) != null) {
                    int loff = (width + mPageMargin) * ii.position;
                    int childLeft = getPaddingLeft() + loff;
                    int childTop = getPaddingTop();
                    if (DEBUG) Log.v(TAG, "Positioning #" + i + " " + child + " f=" + ii.object
    		        + ":" + childLeft + "," + childTop + " " + child.getMeasuredWidth()
    		        + "x" + child.getMeasuredHeight());
                    child.layout(childLeft, childTop,
                            childLeft + child.getMeasuredWidth(),
                            childTop + child.getMeasuredHeight());
                }
            }
            mFirstLayout = false;
        }
    
        @Override
        public void computeScroll() {
            if (DEBUG) Log.i(TAG, "computeScroll: finished=" + mScroller.isFinished());
            if (!mScroller.isFinished()) {
                if (mScroller.computeScrollOffset()) {
                    if (DEBUG) Log.i(TAG, "computeScroll: still scrolling");
                    int oldX = getScrollX();
                    int oldY = getScrollY();
                    int x = mScroller.getCurrX();
                    int y = mScroller.getCurrY();
    
                    if (oldX != x || oldY != y) {
                        scrollTo(x, y);
                    }
    
                    if (mOnPageChangeListener != null) {
                        final int widthWithMargin = getWidth() + mPageMargin;
                        final int position = x / widthWithMargin;
                        final int offsetPixels = x % widthWithMargin;
                        final float offset = (float) offsetPixels / widthWithMargin;
                        mOnPageChangeListener.onPageScrolled(position, offset, offsetPixels);
                    }
    
                    // Keep on drawing until the animation has finished.
                    invalidate();
                    return;
                }
            }
    
            // Done with scroll, clean up state.
            completeScroll();
        }
    
        private void completeScroll() {
            boolean needPopulate = mScrolling;
            if (needPopulate) {
                // Done with scroll, no longer want to cache view drawing.
                setScrollingCacheEnabled(false);
                mScroller.abortAnimation();
                int oldX = getScrollX();
                int oldY = getScrollY();
                int x = mScroller.getCurrX();
                int y = mScroller.getCurrY();
                if (oldX != x || oldY != y) {
                    scrollTo(x, y);
                }
                setScrollState(SCROLL_STATE_IDLE);
            }
            mPopulatePending = false;
            mScrolling = false;
            for (int i=0; i<mItems.size(); i++) {
                ItemInfo ii = mItems.get(i);
                if (ii.scrolling) {
                    needPopulate = true;
                    ii.scrolling = false;
                }
            }
            if (needPopulate) {
                populate();
            }
        }
    
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            /*
             * This method JUST determines whether we want to intercept the motion.
             * If we return true, onMotionEvent will be called and we do the actual
             * scrolling there.
             */
    
            final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
    
            // Always take care of the touch gesture being complete.
            if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
                // Release the drag.
                if (DEBUG) Log.v(TAG, "Intercept done!");
                mIsBeingDragged = false;
                mIsUnableToDrag = false;
                mActivePointerId = INVALID_POINTER;
                return false;
            }
    
            // Nothing more to do here if we have decided whether or not we
            // are dragging.
            if (action != MotionEvent.ACTION_DOWN) {
                if (mIsBeingDragged) {
                    if (DEBUG) Log.v(TAG, "Intercept returning true!");
                    return true;
                }
                if (mIsUnableToDrag) {
                    if (DEBUG) Log.v(TAG, "Intercept returning false!");
                    return false;
                }
            }
    
            switch (action) {
                case MotionEvent.ACTION_MOVE: {
                    /*
                     * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                     * whether the user has moved far enough from his original down touch.
                     */
    
                    /*
                    * Locally do absolute value. mLastMotionY is set to the y value
                    * of the down event.
                    */
                    final int activePointerId = mActivePointerId;
                    if (activePointerId == INVALID_POINTER) {
                        // If we don't have a valid id, the touch down wasn't on content.
                        break;
                    }
    
                    final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
                    final float x = MotionEventCompat.getX(ev, pointerIndex);
                    final float dx = x - mLastMotionX;
                    final float xDiff = Math.abs(dx);
                    final float y = MotionEventCompat.getY(ev, pointerIndex);
                    final float yDiff = Math.abs(y - mLastMotionY);
                    final int scrollX = getScrollX();
                    final boolean atEdge = (dx > 0 && scrollX == 0) || (dx < 0 && mAdapter != null &&
                            scrollX >= (mAdapter.getCount() - 1) * getWidth() - 1);
                    if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
    
                    if (canScroll(this, false, (int) dx, (int) x, (int) y)) {
                        // Nested view has scrollable area under this point. Let it be handled there.
                        mInitialMotionX = mLastMotionX = x;
                        mLastMotionY = y;
                        return false;
                    }
                    if (xDiff > mTouchSlop && xDiff > yDiff) {
                        if (DEBUG) Log.v(TAG, "Starting drag!");
                        mIsBeingDragged = true;
                        setScrollState(SCROLL_STATE_DRAGGING);
                        mLastMotionX = x;
                        setScrollingCacheEnabled(true);
                    } else {
                        if (yDiff > mTouchSlop) {
                            // The finger has moved enough in the vertical
                            // direction to be counted as a drag...  abort
                            // any attempt to drag horizontally, to work correctly
                            // with children that have scrolling containers.
                            if (DEBUG) Log.v(TAG, "Starting unable to drag!");
                            mIsUnableToDrag = true;
                        }
                    }
                    break;
                }
    
                case MotionEvent.ACTION_DOWN: {
                    /*
                     * Remember location of down touch.
                     * ACTION_DOWN always refers to pointer index 0.
                     */
                    mLastMotionX = mInitialMotionX = ev.getX();
                    mLastMotionY = ev.getY();
                    mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
    
                    if (mScrollState == SCROLL_STATE_SETTLING) {
                        // Let the user 'catch' the pager as it animates.
                        mIsBeingDragged = true;
                        mIsUnableToDrag = false;
                        setScrollState(SCROLL_STATE_DRAGGING);
                    } else {
                        completeScroll();
                        mIsBeingDragged = false;
                        mIsUnableToDrag = false;
                    }
    
                    if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
                            + " mIsBeingDragged=" + mIsBeingDragged
                            + "mIsUnableToDrag=" + mIsUnableToDrag);
                    break;
                }
    
                case MotionEventCompat.ACTION_POINTER_UP:
                    onSecondaryPointerUp(ev);
                    break;
            }
    
            /*
            * The only time we want to intercept motion events is if we are in the
            * drag mode.
            */
            return mIsBeingDragged;
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (mFakeDragging) {
                // A fake drag is in progress already, ignore this real one
                // but still eat the touch events.
                // (It is likely that the user is multi-touching the screen.)
                return true;
            }
    
            if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
                // Don't handle edge touches immediately -- they may actually belong to one of our
                // descendants.
                return false;
            }
    
            if (mAdapter == null || mAdapter.getCount() == 0) {
                // Nothing to present or scroll; nothing to touch.
                return false;
            }
    
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            mVelocityTracker.addMovement(ev);
    
            final int action = ev.getAction();
            boolean needsInvalidate = false;
    
            switch (action & MotionEventCompat.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN: {
                    /*
                     * If being flinged and user touches, stop the fling. isFinished
                     * will be false if being flinged.
                     */
                    completeScroll();
    
                    // Remember where the motion event started
                    mLastMotionX = mInitialMotionX = ev.getX();
                    mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                    break;
                }
                case MotionEvent.ACTION_MOVE:
                    if (!mIsBeingDragged) {
                        final int pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                        final float x = MotionEventCompat.getX(ev, pointerIndex);
                        final float xDiff = Math.abs(x - mLastMotionX);
                        final float y = MotionEventCompat.getY(ev, pointerIndex);
                        final float yDiff = Math.abs(y - mLastMotionY);
                        if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
                        if (xDiff > mTouchSlop && xDiff > yDiff) {
                            if (DEBUG) Log.v(TAG, "Starting drag!");
                            mIsBeingDragged = true;
                            mLastMotionX = x;
                            setScrollState(SCROLL_STATE_DRAGGING);
                            setScrollingCacheEnabled(true);
                        }
                    }
                    if (mIsBeingDragged) {
                        // Scroll to follow the motion event
                        final int activePointerIndex = MotionEventCompat.findPointerIndex(
                                ev, mActivePointerId);
                        final float x = MotionEventCompat.getX(ev, activePointerIndex);
                        final float deltaX = mLastMotionX - x;
                        mLastMotionX = x;
                        float oldScrollX = getScrollX();
                        float scrollX = oldScrollX + deltaX;
                        final int width = getWidth();
                        final int widthWithMargin = width + mPageMargin;
    
                        final int lastItemIndex = mAdapter.getCount() - 1;
                        final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
                        final float rightBound =
                                Math.min(mCurItem + 1, lastItemIndex) * widthWithMargin;
                        if (scrollX < leftBound) {
                            if (leftBound == 0) {
                                float over = -scrollX;
                                needsInvalidate = mLeftEdge.onPull(over / width);
                            }
                            scrollX = leftBound;
                        } else if (scrollX > rightBound) {
                            if (rightBound == lastItemIndex * widthWithMargin) {
                                float over = scrollX - rightBound;
                                needsInvalidate = mRightEdge.onPull(over / width);
                            }
                            scrollX = rightBound;
                        }
                        // Don't lose the rounded component
                        mLastMotionX += scrollX - (int) scrollX;
                        scrollTo((int) scrollX, getScrollY());
                        if (mOnPageChangeListener != null) {
                            final int position = (int) scrollX / widthWithMargin;
                            final int positionOffsetPixels = (int) scrollX % widthWithMargin;
                            final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
                            mOnPageChangeListener.onPageScrolled(position, positionOffset,
                                    positionOffsetPixels);
                        }
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    if (mIsBeingDragged) {
                        final VelocityTracker velocityTracker = mVelocityTracker;
                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                        int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
                                velocityTracker, mActivePointerId);
                        mPopulatePending = true;
                        final int widthWithMargin = getWidth() + mPageMargin;
                        final int scrollX = getScrollX();
                        final int currentPage = scrollX / widthWithMargin;
                        int nextPage = initialVelocity > 0 ? currentPage : currentPage + 1;
                        setCurrentItemInternal(nextPage, true, true, initialVelocity);
    
                        mActivePointerId = INVALID_POINTER;
                        endDrag();
                        needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
                    }
                    break;
                case MotionEvent.ACTION_CANCEL:
                    if (mIsBeingDragged) {
                        setCurrentItemInternal(mCurItem, true, true);
                        mActivePointerId = INVALID_POINTER;
                        endDrag();
                        needsInvalidate = mLeftEdge.onRelease() | mRightEdge.onRelease();
                    }
                    break;
                case MotionEventCompat.ACTION_POINTER_DOWN: {
                    final int index = MotionEventCompat.getActionIndex(ev);
                    final float x = MotionEventCompat.getX(ev, index);
                    mLastMotionX = x;
                    mActivePointerId = MotionEventCompat.getPointerId(ev, index);
                    break;
                }
                case MotionEventCompat.ACTION_POINTER_UP:
                    onSecondaryPointerUp(ev);
                    mLastMotionX = MotionEventCompat.getX(ev,
                            MotionEventCompat.findPointerIndex(ev, mActivePointerId));
                    break;
            }
            if (needsInvalidate) {
                invalidate();
            }
            return true;
        }
    
        @Override
        public void draw(Canvas canvas) {
            super.draw(canvas);
            boolean needsInvalidate = false;
    
            final int overScrollMode = ViewCompat.getOverScrollMode(this);
            if (overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS ||
                    (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS &&
                            mAdapter != null && mAdapter.getCount() > 1)) {
                if (!mLeftEdge.isFinished()) {
                    final int restoreCount = canvas.save();
                    final int height = getHeight() - getPaddingTop() - getPaddingBottom();
    
                    canvas.rotate(270);
                    canvas.translate(-height + getPaddingTop(), 0);
                    mLeftEdge.setSize(height, getWidth());
                    needsInvalidate |= mLeftEdge.draw(canvas);
                    canvas.restoreToCount(restoreCount);
                }
                if (!mRightEdge.isFinished()) {
                    final int restoreCount = canvas.save();
                    final int width = getWidth();
                    final int height = getHeight() - getPaddingTop() - getPaddingBottom();
                    final int itemCount = mAdapter != null ? mAdapter.getCount() : 1;
    
                    canvas.rotate(90);
                    canvas.translate(-getPaddingTop(),
                            -itemCount * (width + mPageMargin) + mPageMargin);
                    mRightEdge.setSize(height, width);
                    needsInvalidate |= mRightEdge.draw(canvas);
                    canvas.restoreToCount(restoreCount);
                }
            } else {
                mLeftEdge.finish();
                mRightEdge.finish();
            }
    
            if (needsInvalidate) {
                // Keep animating
                invalidate();
            }
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            // Draw the margin drawable if needed.
            if (mPageMargin > 0 && mMarginDrawable != null) {
                final int scrollX = getScrollX();
                final int width = getWidth();
                final int offset = scrollX % (width + mPageMargin);
                if (offset != 0) {
                    // Pages fit completely when settled; we only need to draw when in between
                    final int left = scrollX - offset + width;
                    mMarginDrawable.setBounds(left, 0, left + mPageMargin, getHeight());
                    mMarginDrawable.draw(canvas);
                }
            }
        }
    
        /**
         * Start a fake drag of the pager.
         *
         * <p>A fake drag can be useful if you want to synchronize the motion of the ViewPager
         * with the touch scrolling of another view, while still letting the ViewPager
         * control the snapping motion and fling behavior. (e.g. parallax-scrolling tabs.)
         * Call {@link #fakeDragBy(float)} to simulate the actual drag motion. Call
         * {@link #endFakeDrag()} to complete the fake drag and fling as necessary.
         *
         * <p>During a fake drag the ViewPager will ignore all touch events. If a real drag
         * is already in progress, this method will return false.
         *
         * @return true if the fake drag began successfully, false if it could not be started.
         *
         * @see #fakeDragBy(float)
         * @see #endFakeDrag()
         */
        public boolean beginFakeDrag() {
            if (mIsBeingDragged) {
                return false;
            }
            mFakeDragging = true;
            setScrollState(SCROLL_STATE_DRAGGING);
            mInitialMotionX = mLastMotionX = 0;
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            } else {
                mVelocityTracker.clear();
            }
            final long time = SystemClock.uptimeMillis();
            final MotionEvent ev = MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0, 0, 0);
            mVelocityTracker.addMovement(ev);
            ev.recycle();
            mFakeDragBeginTime = time;
            return true;
        }
    
        /**
         * End a fake drag of the pager.
         *
         * @see #beginFakeDrag()
         * @see #fakeDragBy(float)
         */
        public void endFakeDrag() {
            if (!mFakeDragging) {
                throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
            }
    
            final VelocityTracker velocityTracker = mVelocityTracker;
            velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
            int initialVelocity = (int)VelocityTrackerCompat.getYVelocity(
                    velocityTracker, mActivePointerId);
            mPopulatePending = true;
            if ((Math.abs(initialVelocity) > mMinimumVelocity)
                    || Math.abs(mInitialMotionX-mLastMotionX) >= (getWidth()/3)) {
                if (mLastMotionX > mInitialMotionX) {
                    setCurrentItemInternal(mCurItem-1, true, true);
                } else {
                    setCurrentItemInternal(mCurItem+1, true, true);
                }
            } else {
                setCurrentItemInternal(mCurItem, true, true);
            }
            endDrag();
    
            mFakeDragging = false;
        }
    
        /**
         * Fake drag by an offset in pixels. You must have called {@link #beginFakeDrag()} first.
         *
         * @param xOffset Offset in pixels to drag by.
         * @see #beginFakeDrag()
         * @see #endFakeDrag()
         */
        public void fakeDragBy(float xOffset) {
            if (!mFakeDragging) {
                throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
            }
    
            mLastMotionX += xOffset;
            float scrollX = getScrollX() - xOffset;
            final int width = getWidth();
            final int widthWithMargin = width + mPageMargin;
    
            final float leftBound = Math.max(0, (mCurItem - 1) * widthWithMargin);
            final float rightBound =
                    Math.min(mCurItem + 1, mAdapter.getCount() - 1) * widthWithMargin;
            if (scrollX < leftBound) {
                scrollX = leftBound;
            } else if (scrollX > rightBound) {
                scrollX = rightBound;
            }
            // Don't lose the rounded component
            mLastMotionX += scrollX - (int) scrollX;
            scrollTo((int) scrollX, getScrollY());
            if (mOnPageChangeListener != null) {
                final int position = (int) scrollX / widthWithMargin;
                final int positionOffsetPixels = (int) scrollX % widthWithMargin;
                final float positionOffset = (float) positionOffsetPixels / widthWithMargin;
                mOnPageChangeListener.onPageScrolled(position, positionOffset,
                        positionOffsetPixels);
            }
    
            // Synthesize an event for the VelocityTracker.
            final long time = SystemClock.uptimeMillis();
            final MotionEvent ev = MotionEvent.obtain(mFakeDragBeginTime, time, MotionEvent.ACTION_MOVE,
                    mLastMotionX, 0, 0);
            mVelocityTracker.addMovement(ev);
            ev.recycle();
        }
    
        /**
         * Returns true if a fake drag is in progress.
         *
         * @return true if currently in a fake drag, false otherwise.
         *
         * @see #beginFakeDrag()
         * @see #fakeDragBy(float)
         * @see #endFakeDrag()
         */
        public boolean isFakeDragging() {
            return mFakeDragging;
        }
    
        private void onSecondaryPointerUp(MotionEvent ev) {
            final int pointerIndex = MotionEventCompat.getActionIndex(ev);
            final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
            if (pointerId == mActivePointerId) {
                // This was our active pointer going up. Choose a new
                // active pointer and adjust accordingly.
                final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex);
                mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
                if (mVelocityTracker != null) {
                    mVelocityTracker.clear();
                }
            }
        }
    
        private void endDrag() {
            mIsBeingDragged = false;
            mIsUnableToDrag = false;
    
            if (mVelocityTracker != null) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
            }
        }
    
        private void setScrollingCacheEnabled(boolean enabled) {
            if (mScrollingCacheEnabled != enabled) {
                mScrollingCacheEnabled = enabled;
                if (USE_CACHE) {
                    final int size = getChildCount();
                    for (int i = 0; i < size; ++i) {
                        final View child = getChildAt(i);
                        if (child.getVisibility() != GONE) {
                            child.setDrawingCacheEnabled(enabled);
                        }
                    }
                }
            }
        }
    
        /**
         * Tests scrollability within child views of v given a delta of dx.
         *
         * @param v View to test for horizontal scrollability
         * @param checkV Whether the view v passed should itself be checked for scrollability (true),
         *               or just its children (false).
         * @param dx Delta scrolled in pixels
         * @param x X coordinate of the active touch point
         * @param y Y coordinate of the active touch point
         * @return true if child views of v can be scrolled by delta of dx.
         */
        protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
            if (v instanceof ViewGroup) {
                final ViewGroup group = (ViewGroup) v;
                final int scrollX = v.getScrollX();
                final int scrollY = v.getScrollY();
                final int count = group.getChildCount();
                // Count backwards - let topmost views consume scroll distance first.
                for (int i = count - 1; i >= 0; i--) {
                    // TODO: Add versioned support here for transformed views.
                    // This will not work for transformed views in Honeycomb+
                    final View child = group.getChildAt(i);
                    if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() &&
                            y + scrollY >= child.getTop() && y + scrollY < child.getBottom() &&
                            canScroll(child, true, dx, x + scrollX - child.getLeft(),
                                    y + scrollY - child.getTop())) {
                        return true;
                    }
                }
            }
    
            return checkV && ViewCompat.canScrollHorizontally(v, -dx);
        }
    
        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            // Let the focused view and/or our descendants get the key first
            return super.dispatchKeyEvent(event) || executeKeyEvent(event);
        }
    
        /**
         * You can call this function yourself to have the scroll view perform
         * scrolling from a key event, just as if the event had been dispatched to
         * it by the view hierarchy.
         *
         * @param event The key event to execute.
         * @return Return true if the event was handled, else false.
         */
        public boolean executeKeyEvent(KeyEvent event) {
            boolean handled = false;
            if (event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (event.getKeyCode()) {
                    case KeyEvent.KEYCODE_DPAD_LEFT:
                        handled = arrowScroll(FOCUS_LEFT);
                        break;
                    case KeyEvent.KEYCODE_DPAD_RIGHT:
                        handled = arrowScroll(FOCUS_RIGHT);
                        break;
                    case KeyEvent.KEYCODE_TAB:
                        if (KeyEventCompat.hasNoModifiers(event)) {
                            handled = arrowScroll(FOCUS_FORWARD);
                        } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
                            handled = arrowScroll(FOCUS_BACKWARD);
                        }
                        break;
                }
            }
            return handled;
        }
    
        public boolean arrowScroll(int direction) {
            View currentFocused = findFocus();
            if (currentFocused == this) currentFocused = null;
    
            boolean handled = false;
    
            View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused,
                    direction);
            if (nextFocused != null && nextFocused != currentFocused) {
                if (direction == View.FOCUS_LEFT) {
                    // If there is nothing to the left, or this is causing us to
                    // jump to the right, then what we really want to do is page left.
                    if (currentFocused != null && nextFocused.getLeft() >= currentFocused.getLeft()) {
                        handled = pageLeft();
                    } else {
                        handled = nextFocused.requestFocus();
                    }
                } else if (direction == View.FOCUS_RIGHT) {
                    // If there is nothing to the right, or this is causing us to
                    // jump to the left, then what we really want to do is page right.
                    if (currentFocused != null && nextFocused.getLeft() <= currentFocused.getLeft()) {
                        handled = pageRight();
                    } else {
                        handled = nextFocused.requestFocus();
                    }
                }
            } else if (direction == FOCUS_LEFT || direction == FOCUS_BACKWARD) {
                // Trying to move left and nothing there; try to page.
                handled = pageLeft();
            } else if (direction == FOCUS_RIGHT || direction == FOCUS_FORWARD) {
                // Trying to move right and nothing there; try to page.
                handled = pageRight();
            }
            if (handled) {
                playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction));
            }
            return handled;
        }
    
        boolean pageLeft() {
            if (mCurItem > 0) {
                setCurrentItem(mCurItem-1, true);
                return true;
            }
            return false;
        }
    
        boolean pageRight() {
            if (mAdapter != null && mCurItem < (mAdapter.getCount()-1)) {
                setCurrentItem(mCurItem+1, true);
                return true;
            }
            return false;
        }
    
        /**
         * We only want the current page that is being shown to be focusable.
         */
        @Override
        public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
            final int focusableCount = views.size();
    
            final int descendantFocusability = getDescendantFocusability();
    
            if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) {
                for (int i = 0; i < getChildCount(); i++) {
                    final View child = getChildAt(i);
                    if (child.getVisibility() == VISIBLE) {
                        ItemInfo ii = infoForChild(child);
                        if (ii != null && ii.position == mCurItem) {
                            child.addFocusables(views, direction, focusableMode);
                        }
                    }
                }
            }
    
            // we add ourselves (if focusable) in all cases except for when we are
            // FOCUS_AFTER_DESCENDANTS and there are some descendants focusable.  this is
            // to avoid the focus search finding layouts when a more precise search
            // among the focusable children would be more interesting.
            if (
                descendantFocusability != FOCUS_AFTER_DESCENDANTS ||
                    // No focusable descendants
                    (focusableCount == views.size())) {
                // Note that we can't call the superclass here, because it will
                // add all views in.  So we need to do the same thing View does.
                if (!isFocusable()) {
                    return;
                }
                if ((focusableMode & FOCUSABLES_TOUCH_MODE) == FOCUSABLES_TOUCH_MODE &&
                        isInTouchMode() && !isFocusableInTouchMode()) {
                    return;
                }
                if (views != null) {
                    views.add(this);
                }
            }
        }
    
        /**
         * We only want the current page that is being shown to be touchable.
         */
        @Override
        public void addTouchables(ArrayList<View> views) {
            // Note that we don't call super.addTouchables(), which means that
            // we don't call View.addTouchables().  This is okay because a ViewPager
            // is itself not touchable.
            for (int i = 0; i < getChildCount(); i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() == VISIBLE) {
                    ItemInfo ii = infoForChild(child);
                    if (ii != null && ii.position == mCurItem) {
                        child.addTouchables(views);
                    }
                }
            }
        }
    
        /**
         * We only want the current page that is being shown to be focusable.
         */
        @Override
        protected boolean onRequestFocusInDescendants(int direction,
                Rect previouslyFocusedRect) {
            int index;
            int increment;
            int end;
            int count = getChildCount();
            if ((direction & FOCUS_FORWARD) != 0) {
                index = 0;
                increment = 1;
                end = count;
            } else {
                index = count - 1;
                increment = -1;
                end = -1;
            }
            for (int i = index; i != end; i += increment) {
                View child = getChildAt(i);
                if (child.getVisibility() == VISIBLE) {
                    ItemInfo ii = infoForChild(child);
                    if (ii != null && ii.position == mCurItem) {
                        if (child.requestFocus(direction, previouslyFocusedRect)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    
        @Override
        public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
            // ViewPagers should only report accessibility info for the current page,
            // otherwise things get very confusing.
    
            // TODO: Should this note something about the paging container?
    
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() == VISIBLE) {
                    final ItemInfo ii = infoForChild(child);
                    if (ii != null && ii.position == mCurItem &&
                            child.dispatchPopulateAccessibilityEvent(event)) {
                        return true;
                    }
                }
            }
    
            return false;
        }
    
        private class PagerObserver extends DataSetObserver {
    
            @Override
            public void onChanged() {
                dataSetChanged();
            }
    
            @Override
            public void onInvalidated() {
                dataSetChanged();
            }
        }
    }
    
    


  • 相关阅读:
    非对称加密-RSA公钥加密,私钥解密,私钥加签,公钥验签
    设置mysql数据库本地连接或外部可连接
    mysql自增长主键,删除数据后,将主键顺序重新排序
    非Service层和Controller层调用ssm框架中的方法
    DES加密算法(密文只有字符串和数字)java和android加密的结果一致(可放在url中)
    SpringBoot ajax Restful整合
    java中线程执行流程详解
    在 CSS 中直接引用 fontawesome 图标(附码表)
    C++内存管理~
    操作系统那些事儿
  • 原文地址:https://www.cnblogs.com/wanghang/p/6299593.html
Copyright © 2011-2022 走看看