ViewPager源码解惑

作者: 阿飞咯 | 来源:发表于2016-04-10 18:01 被阅读2523次

    带着疑惑去源码中寻找答案。

    1.ViewPager实现思想。
    2.Fragment事务是如何管理的。
    3.FragmentStatePagerAdapter与FragmentPagerAdapter对比

    学习完ViewPager源码,我可以做什么?
    1.定义一个展示Image的PagerAdapter
    2.如何使ViewPager做到循环效果

    下面开始分析:
    首先需要了解

        static class ItemInfo {
            Object object;
            int position;
            boolean scrolling;
            float widthFactor;
            float offset;
        }
    private final ArrayList<ItemInfo> mItems = new ArrayList<ItemInfo>();
    int mCurItem
    

    mCurItem:表示当前页面的位置

    mItems:表示已经缓存的页面信息(通常会缓存当前显示页面以前当前页面前后页面,不过缓存页面的数量由mOffscreenPageLimit决定)

    ItemInfo:这是用来保存页面信息的
    {
    Object表示页面展示的内容
    position表示该页面的页码
    scrolling表示是否正在滚动
    widthFactor表示加载的页面占ViewPager所占的比例[0~1](默认返回1)
    offset表示页面偏移量
    }

    最先从setAdapter开始。

     /**
         * Set a PagerAdapter that will supply views for this pager as needed.
         *
         * @param adapter Adapter to use
         */
        public void setAdapter(PagerAdapter adapter) {
            if (mAdapter != null) {
                mAdapter.setViewPagerObserver(null);
                mAdapter.startUpdate(this);
                //如果原有adapter就将其完全销毁,调用了destoryItem(),这里mark一下等会剖析destoryItem()源码。
                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();
                removeNonDecorViews();
                mCurItem = 0;
                scrollTo(0, 0);
            }
    
            final PagerAdapter oldAdapter = mAdapter;
            mAdapter = adapter;
            mExpectedAdapterCount = 0;
    
            if (mAdapter != null) {
                if (mObserver == null) {
                    mObserver = new PagerObserver();
                }
                mAdapter.setViewPagerObserver(mObserver);
                mPopulatePending = false;
                final boolean wasFirstLayout = mFirstLayout;
                mFirstLayout = true;
                mExpectedAdapterCount = mAdapter.getCount();
                if (mRestoredCurItem >= 0) {
                  //读取保存的状态
                    mAdapter.restoreState(mRestoredAdapterState, mRestoredClassLoader);
                    setCurrentItemInternal(mRestoredCurItem, false, true);
                    mRestoredCurItem = -1;
                    mRestoredAdapterState = null;
                    mRestoredClassLoader = null;
                } else if (!wasFirstLayout) {
                  //这里需要mark一下 核心方法
                    populate();
                } else {
                    requestLayout();
                }
            }
    
            if (mAdapterChangeListener != null && oldAdapter != adapter) {
                mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
            }
        }
    

    上一步就是简单的对ViewPager设置Adapter,如果原有Adapter则必须将其内容销毁(具体语法后面对比FragmentStatePagerAdapter与FragmentPagerAdapter时分析)。
    现在看一下populate()方法

     void populate(int newCurrentItem) {
            ItemInfo oldCurInfo = null;
            if (mCurItem != newCurrentItem) {
              //获取oldItem的信息(猜测以后会销毁的)
                oldCurInfo = infoForPosition(mCurItem);
                mCurItem = newCurrentItem;
            }
    
            if (mAdapter == null) {
                sortChildDrawingOrder();
                return;
            }
    
            // 如果填充时我们界面正在滑动就停止填充
            if (mPopulatePending) {
                if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
                sortChildDrawingOrder();
                return;
            }
    
            // 与窗口关联之前不填充数据
            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);
    
            // 表示当前页面在items(预加载缓存页面)中的位置
            int curIndex = -1;
           // 表示当前页面的信息
            ItemInfo curItem = null;
            for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
                //查看我们需要的页面是否已经加载在内存中。
                final ItemInfo ii = mItems.get(curIndex);
                if (ii.position >= mCurItem) {
                    if (ii.position == mCurItem) curItem = ii;
                    break;
                }
            }
            //如果mItems中没有对应item,则从mAdapter.instantiateItem()中获取对应item数据。
            if (curItem == null && N > 0) {
                //mark一下等会看这个方法
                curItem = addNewItem(mCurItem, curIndex);
            }
    
            // 这里需要注意了,这里会缓冲当前item的前一个和后一个item。
            if (curItem != null) {
                float extraWidthLeft = 0.f;
                int itemIndex = curIndex - 1;
              //获取缓存中前一个页面的信息
                ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                final int clientWidth = getClientWidth();
                //计算需要额外加载页面的偏移因子
                final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                        2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
               //销毁左边不需要展示的页面(就是除了前一个item),如果左边一个item不在内存中则需要加载到内存
                for (int pos = mCurItem - 1; pos >= 0; pos--) {
                  //如果超出了预加载范围并且超过了设定的预加载页面数量并且  
                  //存在items(预加载页面缓存)中就destory该页面,这里的设计十分巧妙,  
                 // 需要细细的去研读一下。
                    if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
                  //因为预加载的内存是连续的,如果当前为null表示也没有多余信息缓存了
                        if (ii == null) {
                            break;
                        }
                        if (pos == ii.position && !ii.scrolling) {
                            mItems.remove(itemIndex);
                            mAdapter.destroyItem(this, pos, ii.object);
                            if (DEBUG) {
                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
                                        " view: " + ((View) ii.object));
                            }
                            itemIndex--;
                            curIndex--;
                            ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                        }
                    } else if (ii != null && pos == ii.position) {
                        extraWidthLeft += ii.widthFactor;
                        itemIndex--;
                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                    } else {
                        ii = addNewItem(pos, itemIndex + 1);
                        extraWidthLeft += ii.widthFactor;
                        curIndex++;
                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                    }
                }
    
                float extraWidthRight = curItem.widthFactor;
                itemIndex = curIndex + 1;
                if (extraWidthRight < 2.f) {
                    ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    final float rightWidthNeeded = clientWidth <= 0 ? 0 :
                            (float) getPaddingRight() / (float) clientWidth + 2.f;
                  //销毁右边不需要展示的页面(就是除了后一个item),如果右边一个item不在内存中则需要加载到内存
                    for (int pos = mCurItem + 1; pos < N; pos++) {
                        if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
                            if (ii == null) {
                                break;
                            }
                            if (pos == ii.position && !ii.scrolling) {
                                mItems.remove(itemIndex);
                                mAdapter.destroyItem(this, pos, ii.object);
                                if (DEBUG) {
                                    Log.i(TAG, "populate() - destroyItem() with pos: " + pos +
                                            " view: " + ((View) ii.object));
                                }
                                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                            }
                        } else if (ii != null && pos == ii.position) {
                            extraWidthRight += ii.widthFactor;
                            itemIndex++;
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        } else {
                            ii = addNewItem(pos, itemIndex);
                            itemIndex++;
                            extraWidthRight += ii.widthFactor;
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        }
                    }
                }
                //通过上面的流程,现在items缓存的只有预加载的几个页面
                //计算当前页面的offset(偏移)
                //mark一下
                calculatePageOffsets(curItem, curIndex, oldCurInfo);
            }
    
            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);
                }
            }
            //通知adpter当前显示的item(进行事务处理,稍后分析)
            mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
    
            mAdapter.finishUpdate(this);
    
            // Check width measurement of current pages and drawing sort order.
            // 这里是为我们新添加到内存中的View设置参数(偏移量)。
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                final View child = getChildAt(i);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                lp.childIndex = i;
                if (!lp.isDecor && lp.widthFactor == 0.f) {
                    // 0 means requery the adapter for this, it doesn't have a valid width.
                    final ItemInfo ii = infoForChild(child);
                    if (ii != null) {
                        lp.widthFactor = ii.widthFactor;
                        lp.position = ii.position;
                    }
                }
            }
             //这里mark一下,为什么需要有绘制先后顺序
            sortChildDrawingOrder();
    
            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(View.FOCUS_FORWARD)) {
                                break;
                            }
                        }
                    }
                }
            }
        }
    

    其实这个方法读完大概也能猜到Viewpager是如何管理以及展示各个页面了吧。
    下面看一下addNewItem方法

        ItemInfo addNewItem(int position, int index) {
            ItemInfo ii = new ItemInfo();
            ii.position = position;
            ii.object = mAdapter.instantiateItem(this, position);
            ii.widthFactor = mAdapter.getPageWidth(position);
            if (index < 0 || index >= mItems.size()) {
                mItems.add(ii);
            } else {
                mItems.add(index, ii);
            }
            return ii;
        }
    

    这里就很明显了,调用了PagerAdapter的instantiateItem方法,这个方法是我们用来设置当前页码所需要展示内容的。
    下面看一看calculatePageOffsets方法

    private void calculatePageOffsets(ItemInfo curItem, int curIndex, ItemInfo oldCurInfo) {
            final int N = mAdapter.getCount();
            final int width = getClientWidth();
            final float marginOffset = width > 0 ? (float) mPageMargin / width : 0;
            // 计算当前item的偏移量
            //根据oldItem计算(这个就自己体会了)
            if (oldCurInfo != null) {
                final int oldCurPosition = oldCurInfo.position;
                // Base offsets off of oldCurInfo.
                if (oldCurPosition < curItem.position) {
                    int itemIndex = 0;
                    ItemInfo ii = null;
                    float offset = oldCurInfo.offset + oldCurInfo.widthFactor + marginOffset;
                    for (int pos = oldCurPosition + 1;
                            pos <= curItem.position && itemIndex < mItems.size(); pos++) {
                        ii = mItems.get(itemIndex);
                        while (pos > ii.position && itemIndex < mItems.size() - 1) {
                            itemIndex++;
                            ii = mItems.get(itemIndex);
                        }
                        while (pos < ii.position) {
                            // We don't have an item populated for this,
                            // ask the adapter for an offset.
                            offset += mAdapter.getPageWidth(pos) + marginOffset;
                            pos++;
                        }
                        ii.offset = offset;
                        offset += ii.widthFactor + marginOffset;
                    }
                } else if (oldCurPosition > curItem.position) {
                    int itemIndex = mItems.size() - 1;
                    ItemInfo ii = null;
                    float offset = oldCurInfo.offset;
                    for (int pos = oldCurPosition - 1;
                            pos >= curItem.position && itemIndex >= 0; pos--) {
                        ii = mItems.get(itemIndex);
                        while (pos < ii.position && itemIndex > 0) {
                            itemIndex--;
                            ii = mItems.get(itemIndex);
                        }
                        while (pos > ii.position) {
                            // We don't have an item populated for this,
                            // ask the adapter for an offset.
                            offset -= mAdapter.getPageWidth(pos) + marginOffset;
                            pos--;
                        }
                        offset -= ii.widthFactor + marginOffset;
                        ii.offset = offset;
                    }
                }
            }
    
            // Base all offsets off of curItem.
            final int itemCount = mItems.size();
            float offset = curItem.offset;
            int pos = curItem.position - 1;
            mFirstOffset = curItem.position == 0 ? curItem.offset : -Float.MAX_VALUE;
            mLastOffset = curItem.position == N - 1 ?
                    curItem.offset + curItem.widthFactor - 1 : Float.MAX_VALUE;
            // 计算前面页面的偏移量(根据当前页面计算)
            for (int i = curIndex - 1; i >= 0; i--, pos--) {
                final ItemInfo ii = mItems.get(i);
                while (pos > ii.position) {
                    offset -= mAdapter.getPageWidth(pos--) + marginOffset;
                }
                offset -= ii.widthFactor + marginOffset;
                ii.offset = offset;
                if (ii.position == 0) mFirstOffset = offset;
            }
            offset = curItem.offset + curItem.widthFactor + marginOffset;
            pos = curItem.position + 1;
            //  计算后面页面的偏移量(根据当前页面计算)
            for (int i = curIndex + 1; i < itemCount; i++, pos++) {
                final ItemInfo ii = mItems.get(i);
                while (pos < ii.position) {
                    offset += mAdapter.getPageWidth(pos++) + marginOffset;
                }
                if (ii.position == N - 1) {
                    mLastOffset = offset + ii.widthFactor - 1;
                }
                ii.offset = offset;
                offset += ii.widthFactor + marginOffset;
            }
    
            mNeedCalculatePageOffsets = false;
        }
    

    那么偏移量计算出来有什么用呢?等会分析..
    下面看另外一个方法sortChildDrawingOrder

        private void sortChildDrawingOrder() {
            if (mDrawingOrder != DRAW_ORDER_DEFAULT) {
                if (mDrawingOrderedChildren == null) {
                    mDrawingOrderedChildren = new ArrayList<View>();
                } else {
                    mDrawingOrderedChildren.clear();
                }
                final int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    final View child = getChildAt(i);
                    mDrawingOrderedChildren.add(child);
                }
                //这里是根据View的position对View进行的一个排序,这里为什么要排序呢?先保留疑问
                Collections.sort(mDrawingOrderedChildren, sPositionComparator);
            }
        }
    

    到这里基本把最核心的方法populate分析完。
    我们在梳理一下流程:
    1.更新items,将items中的内容换成当前展示页面以及预缓存页面(有序)。这里会直接或间接调用PagerAdapter的startUpdate()、instantiateItem()、destroyItem()、setPrimaryItem()、finishUpdate()等等方法,说明ViewPager的扩展性的强大,在这几个方法中我们必须管理好我们的子View。具体可以参考一下FragmentPagerAdapter和FragmentStatePagerAdapter。
    2.计算每个items的off(偏移量),这个计算出来有什么作用呢?其实猜都能猜出来肯定绘制onLayout()方法中起作用的,等会分析。
    其实该方法主要就是做了这两件事情。
    下面我们分析一下onLayout()方法。

    @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            final int count = getChildCount();
            int width = r - l;
            int height = b - t;
            int paddingLeft = getPaddingLeft();
            int paddingTop = getPaddingTop();
            int paddingRight = getPaddingRight();
            int paddingBottom = getPaddingBottom();
            final int scrollX = getScrollX();
    
            int decorCount = 0;
    
            // First pass - decor views. We need to do this in two passes so that
            // we have the proper offsets for non-decor views later.
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    int childLeft = 0;
                    int childTop = 0;
                    if (lp.isDecor) {
                     //这里就忽略了
                    }
            }
    
            final int childWidth = width - paddingLeft - paddingRight;
            // 从这里看到我们的offset派上用场了。
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    ItemInfo ii;
                    if (!lp.isDecor && (ii = infoForChild(child)) != null) {
                      //这里看出来了我们实际上是利用offset将其展示在当前页面外。从而滑动的时候通过偏移量的设置效果就出来了
                        int loff = (int) (childWidth * ii.offset);
                        int childLeft = paddingLeft + loff;
                        int childTop = paddingTop;
                        if (lp.needsMeasure) {
                            // This was added during layout and needs measurement.
                            // Do it now that we know what we're working with.
                            lp.needsMeasure = false;
                            final int widthSpec = MeasureSpec.makeMeasureSpec(
                                    (int) (childWidth * lp.widthFactor),
                                    MeasureSpec.EXACTLY);
                            final int heightSpec = MeasureSpec.makeMeasureSpec(
                                    (int) (height - paddingTop - paddingBottom),
                                    MeasureSpec.EXACTLY);
                            child.measure(widthSpec, heightSpec);
                        }
                        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());
                    }
                }
            }
            mTopPageBounds = paddingTop;
            mBottomPageBounds = height - paddingBottom;
            mDecorChildCount = decorCount;
    
            if (mFirstLayout) {
                scrollToItem(mCurItem, false, 0, false);
            }
            mFirstLayout = false;
        }
    

    下面就可以顺利的将子View绘制出来了,这就是大概的流程。仔细去研究内部滑动源码或者setCurrentPage源码都可以发现实际上是调用了populate()方法。当我们需要有View更新的时候比如addView()、removeView()都会进行requestLayout()、以及invalidate()。

    下面直观的绘制一下流程:
    当一个页面显示 偏移量为0


    第一个页面显示

    第二个页面显示 偏移量为offset


    第二个页面显示.png
    第三个页面显示 偏移量为2*offset
    第三个页面显示第一个页面destory.png
    后面的就不做分析了,上面的图直观的表示出了Viewpager对其item的管理。

    下面对比一下FragmentStatePagerAdapter和FragmentPagerAdapter
    主要对比一下instantiateItem()与destoryItem()以及saveState()。

    FragmentStatePagerAdapter

     public Object instantiateItem(ViewGroup container, int position) {
            // If we already have this item instantiated, there is nothing
            // to do.  This can happen when we are restoring the entire pager
            // from its saved state, where the fragment manager has already
            // taken care of restoring the fragments we previously had instantiated.
            if (mFragments.size() > position) {
                Fragment f = mFragments.get(position);
                if (f != null) {
                    return f;
                }
            }
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
    
            Fragment fragment = getItem(position);
            if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
            if (mSavedState.size() > position) {
                Fragment.SavedState fss = mSavedState.get(position);
                if (fss != null) {
                    fragment.setInitialSavedState(fss);
                }
            }
            while (mFragments.size() <= position) {
                mFragments.add(null);
            }
            fragment.setMenuVisibility(false);
            fragment.setUserVisibleHint(false);
            mFragments.set(position, fragment);
            mCurTransaction.add(container.getId(), fragment);
    
            return fragment;
        }
    
    
         public void destroyItem(ViewGroup container, int position, Object object) {
            Fragment fragment = (Fragment) object;
    
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                    + " v=" + ((Fragment)object).getView());
            while (mSavedState.size() <= position) {
                mSavedState.add(null);
            }
            mSavedState.set(position, fragment.isAdded()
                    ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
            mFragments.set(position, null);
    
            mCurTransaction.remove(fragment);
        }
    

    FragmentPagerAdapter

    public Object instantiateItem(ViewGroup container, int position) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
    
            final long itemId = getItemId(position);
    
            // Do we already have this fragment?
            String name = makeFragmentName(container.getId(), itemId);
            Fragment fragment = mFragmentManager.findFragmentByTag(name);
            if (fragment != null) {
                if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
                mCurTransaction.attach(fragment);
            } else {
                fragment = getItem(position);
                if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
                mCurTransaction.add(container.getId(), fragment,
                        makeFragmentName(container.getId(), itemId));
            }
            if (fragment != mCurrentPrimaryItem) {
                fragment.setMenuVisibility(false);
                fragment.setUserVisibleHint(false);
            }
    
            return fragment;
        }
    
    
        public void destroyItem(ViewGroup container, int position, Object object) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }
            if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
                    + " v=" + ((Fragment)object).getView());
            mCurTransaction.detach((Fragment)object);
        }
    

    通过对比我们可以看出最大的区别就是,在FragmentPagerAdapter中只有将Fragment实例交由FragmentManager管理,那么Fragment将一直由FragmentManager维护,而FragmentStatePagerAdapter中Fragment只会是在预加载中由FragmentManager维护,而destory时会被销毁。

    FragmentStatePagerAdapter

    @Override
        public Parcelable saveState() {
            Bundle state = null;
            if (mSavedState.size() > 0) {
                state = new Bundle();
                Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
                mSavedState.toArray(fss);
                state.putParcelableArray("states", fss);
            }
            for (int i=0; i<mFragments.size(); i++) {
                Fragment f = mFragments.get(i);
                if (f != null && f.isAdded()) {
                    if (state == null) {
                        state = new Bundle();
                    }
                    String key = "f" + i;
                    mFragmentManager.putFragment(state, key, f);
                }
            }
            return state;
        }
    
        @Override
        public void restoreState(Parcelable state, ClassLoader loader) {
            if (state != null) {
                Bundle bundle = (Bundle)state;
                bundle.setClassLoader(loader);
                Parcelable[] fss = bundle.getParcelableArray("states");
                mSavedState.clear();
                mFragments.clear();
                if (fss != null) {
                    for (int i=0; i<fss.length; i++) {
                        mSavedState.add((Fragment.SavedState)fss[i]);
                    }
                }
                Iterable<String> keys = bundle.keySet();
                for (String key: keys) {
                    if (key.startsWith("f")) {
                        int index = Integer.parseInt(key.substring(1));
                        Fragment f = mFragmentManager.getFragment(bundle, key);
                        if (f != null) {
                            while (mFragments.size() <= index) {
                                mFragments.add(null);
                            }
                            f.setMenuVisibility(false);
                            mFragments.set(index, f);
                        } else {
                            Log.w(TAG, "Bad fragment at key " + key);
                        }
                    }
                }
            }
        }
    

    这里可以看出FragmentStatePagerAdapter对fragment的操作日志进行了保存。因为FragmentStatePagerAdapter中Fragment的是否在内存中的信息是由fragments集合维持。所以当我们Activity被销毁和重建时必须对该集合进行状态信息的维持。

    通过源码的对比我们可以发现当我们的Fragment数量有限时我们应该使用FragmentPagerAdapter,当我们有很多页面Fragment需要展示的时候则用FragmentStatePagerAdapter。

    1.定义一个展示Image的PagerAdapter

    下面我们可以封装一个用来显示Image的PagerAdapter。
    这是我封装的,仅供参考。

    import android.graphics.Bitmap;
    import android.support.v4.view.PagerAdapter;
    import android.util.Log;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    
    /**
     * Created by xiefei on 2016/4/10.
     */
    public abstract class ImagePagerAdapter extends PagerAdapter{
        private final String Tag = "ImagePagerAdapter";
        //重新利用destory的ImageView
        private ImageView preImageView;
        public ImagePagerAdapter(){
        }
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            ImageView imageView;
            if(preImageView == null){
                imageView = new ImageView(container.getContext());
            }else
                imageView = preImageView;
            preImageView = null;
            Log.d(Tag,imageView.toString());
            imageView.setImageBitmap(getItem(position));
            container.addView(imageView);
            return imageView;
        }
    
        @Override
        public void setPrimaryItem(ViewGroup container, int position, Object object) {
            super.setPrimaryItem(container, position, object);
        }
    
        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            preImageView = (ImageView) object;
            container.removeView(preImageView);
        }
    
        public abstract Bitmap getItem(int position);
        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }
    }
    

    向上面这样封装达到一个优点,就是重用了布局,对需要展示很多的图片信息还是有一定优势的。

    2.如何使ViewPager做到循环效果PagerAdapter

    下面开始第二点,如何定义一个循环ViewPager
    方法一:简单粗暴,直接修改getCount()

    方法二:修改源代码,让ViewPager支持循环
    因为涉及到会改变populate()方法以及其他方法,我只好重新定义一个ViewPager去更改,这部分我总结好了会更新。现在已完成在github上。
    主要更改了populate()、scrollToItem()、determineTargetPage()。希望大家提出修改建议。

    相关文章

      网友评论

      • 白茫茫的大地:当快速滑动的时候,会出现anr的情况
        阿飞咯:@白茫茫的大地 嗯嗯 太久了 主要提供一种思路 多谢补充
        白茫茫的大地:我测试了几次,发现anr的原因是因为在populate的方法中 for (int pos = mCurItem + 1;; pos++) { } 方法中进入了无限循环中了
        阿飞咯:@白茫茫的大地 对 这是viewpager的问题 viewpager页面过多快速滑动也会anr
      • 1ce64413126f:你好 我想问一下,已你发的这段代码为例,取消viewpager 的预加载该修改哪里呢?
        DEFAULT_OFFSCREEN_PAGERS = 0 无效了
        78f2a1bb20ca:@1ce64413126f 拷贝源码修改这个值,否则无法实现哦
        阿飞咯:@1ce64413126f 可以到源码里面修改 默认的是必须大于0
      • 84637d1af8a2:有两页的时候向左滑动出现bug
        84637d1af8a2:@昨天还是一个小白 牛
        阿飞咯:@lwjfork 已更新
        阿飞咯:嗯嗯 多谢提醒 我去修改一下:grin:
      • 05310ca6a6e0:分析很详尽,点个赞~

      本文标题:ViewPager源码解惑

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