1-VIII--ViewPager的基本使用

作者: e4e52c116681 | 来源:发表于2018-08-29 09:10 被阅读4次

    零、前言

    [1].ViewPager顾名思义是将若干视图一页一页的展现
    [2].ViewPager和Fragment郎才女貌,天造之合,在加个TabLayout简直和睦一家人
    [3].本文介绍ViewPager的基本使用


    一、ViewPager的基本使用

    1.activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <android.support.v4.view.ViewPager
            android:id="@+id/vp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
    
    </android.support.constraint.ConstraintLayout>
    
    2.用来填充的布局:item_viewpager.xml
    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:background="@color/colorPrimary"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/id_tv_content"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>
    
    </android.support.constraint.ConstraintLayout>
    
    3.MainActivity
    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
        @BindView(R.id.vp)
        LazyViewPager mVp;
        private ArrayList<View> mViews;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ButterKnife.bind(this);
            //[1].创建容器盛放数据
            mViews = new ArrayList<>();
            for (int i = 0; i < 5; i++) {
                View view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item_viewpager, null);
                view.setBackgroundColor(ColUtils.randomRGB());//自定义随机颜色方法
                TextView tv = view.findViewById(R.id.id_tv_content);
                tv.setText("Pager" + i);
                mViews.add(view);
            }
            //[3]设置适配器,为ViewPager填充数据
            mVp.setAdapter(new MyViewPagerAdapter());
        }
    
        /**
         * [2].适配器类
         */
        class MyViewPagerAdapter extends PagerAdapter {
            /**
             * [2-1]页面数量
             *
             * @return
             */
            @Override
            public int getCount() {
                return mViews.size();
            }
    
            /**
             * [2-2]初始化页面
             *
             * @param container 视图容器
             * @param position  当前位置
             * @return 视图
             */
            @Override
            public Object instantiateItem(ViewGroup container, int position) {
                Log.e(TAG, "instantiateItem-----: " + position);
    
                container.addView(mViews.get(position));
                return mViews.get(position);
            }
    
            /**
             * [2-3]视图页是否和对象相关联
             *
             * @param view   视图页
             * @param object 对象
             * @return 视图页是否和对象相关联
             */
            @Override
            public boolean isViewFromObject(View view, Object object) {
                return view == object;
            }
    
    
            /**
             * [2-4]页面销毁时调用
             *
             * @param container 视图容器
             * @param position  位置
             * @param object 对象
             */
            @Override
            public void destroyItem(ViewGroup container, int position, Object object) {
                container.removeView((View) object);
            }
        }
    
    随机颜色方法
        /**
         * 返回随机颜色
         *
         * @return 随机颜色
         */
        public static int randomRGB() {
            Random random = new Random();
            int r = 30 + random.nextInt(200);
            int g = 30 + random.nextInt(200);
            int b = 30 + random.nextInt(200);
            return Color.rgb( r, g, b);
        }
    
    ViewPager默认加载方式
    ViewPager默认加载方式.png

    屏蔽预加载

    预加载是为了让滑动流畅,预先或事后保证当前页的左右都有页面被缓存(首左,尾右除外)
    某些情况下我们不希望预加载,那就要找出罪魁祸首,是它,就是它:private static final int DEFAULT_OFFSCREEN_PAGES = 1;
    私有静态final的字段,呵呵,改不了了。。。。解决方法:自定义控件把某版的ViewPager全考出来,改个字段


    本文由张风捷特烈原创,转载请注明
    更多安卓技术欢迎访问:https://www.jianshu.com/c/004f3fe34c94
    张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com
    你的喜欢与支持将是我最大的动力

    附录、网上找的一个:LazyViewPager

    package com.toly1994.viii_viewpager;
    
    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;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    
    /**
     * Layout manager that allows the user to flip left and right through pages of
     * data. You supply an implementation of a
     * {@link android.support.v4.view.PagerAdapter} to generate the pages that the
     * view shows.
     * <p/>
     * <p>
     * Note this class is currently under early design and development. The API will
     * likely change in later updates of the compatibility library, requiring
     * changes to the source code of apps when they are compiled against the newer
     * version.
     * </p>
     */
    public class LazyViewPager 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; // ms
    
        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
         * interface with stub implementations of each method. Extend this if you do
         * not intend to override every method of
         */
        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 LazyViewPager(Context context) {
            super(context);
            initViewPager();
        }
    
        public LazyViewPager(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/>
         * <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/>
         * <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(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 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 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/>
         * <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/>
         * <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();
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:1-VIII--ViewPager的基本使用

        本文链接:https://www.haomeiwen.com/subject/ltqhwftx.html