ViewPager+Fragment 预加载和延迟加载问题

项目中遇到了ViewPager+Fragment,特此记录一下踩过的坑。

预加载的一些问题

之前都不知道ViewPager有一个特殊的功能,预加载。会预加载临近的界面,让滑动更加流畅。ViewPager还提供一个方法来设置预加载的界面数。

 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();
        }
    }

通过源码发现当你设置的值小于默认的值的时候,会自动设置成默认的值。

private static final int DEFAULT_OFFSCREEN_PAGES = 1;

由于默认值为1,想要关闭预加载功能是不能通过这个方法实现的。这是我踩过的坑之一。

需求是需要改变界面的背景颜色,遇到一个问题就是当前的界面的颜色改变了,但是相邻的界面背景颜色没有改变,因为预加载过了,相邻界面的UI不会改变。

但是有人就会说到adapter不是有notifyDataSetChanged()方法吗?直接调用这个方法更新一下不就可以吗?我曾经也是这么认为的,踩过的坑之一。直接调用notifyDataSetChanged()是不会更新的,这和recycleView不同。

至于为什么不会更新,就来跟着源码一起看看。

public void notifyDataSetChanged() {
     
        synchronized (this) {
     
            if (mViewPagerObserver != null) {
     
                mViewPagerObserver.onChanged();
            }
        }
        mObservable.notifyChanged();
    }

有个同步锁的方法和一个notifyChanged()方法。不管加锁的方法,进入到notifyChanged()方法去看看。

/**
	* Called when the contents of the data set have changed.  The recipient
  * will obtain the new contents the next time it queries the data set.
  */
public void notifyChanged() {
     
    synchronized(mObservers) {
     
        for (int i = mObservers.size() - 1; i >= 0; i--) {
     
            mObservers.get(i).onChanged();
        }
    }
}

mObservers这个变量里肯定存放了ViewPager的数据信息。看官方的注释信息,翻译过来就是当数据集的内容更改时调用,下次查询时,将获得新数据。这里依然不知道数据是怎么改变的。接着进入onChanged()方法看看。

public abstract class DataSetObserver {
     
  
    public void onChanged() {
     
        // Do nothing
    }
  
    public void onInvalidated() {
     
        // Do nothing
    }
}

直接一个抽象类,那只能找它的子类了。

private class PagerObserver extends DataSetObserver {
     
    PagerObserver() {
     
    }

    @Override
    public void onChanged() {
     
        dataSetChanged();
    }
    @Override
    public void onInvalidated() {
     
        dataSetChanged();
    }
}

再进入dataSetChanged()方法里看看。

void dataSetChanged() {
     
    // This method only gets called if our observer is attached, so mAdapter is non-null.

    final int adapterCount = mAdapter.getCount();
    mExpectedAdapterCount = adapterCount;
    boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1
            && mItems.size() < adapterCount;
    int newCurrItem = mCurItem;

    boolean isUpdating = false;
    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--;

            if (!isUpdating) {
     
                mAdapter.startUpdate(this);
                isUpdating = true;
            }

            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, adapterCount - 1));
                needPopulate = true;
            }
            continue;
        }

        if (ii.position != newPos) {
     
            if (ii.position == mCurItem) {
     
                // Our current item changed position. Follow it.
                newCurrItem = newPos;
            }

            ii.position = newPos;
            needPopulate = true;
        }
    }

    if (isUpdating) {
     
        mAdapter.finishUpdate(this);
    }

    Collections.sort(mItems, COMPARATOR);

    if (needPopulate) {
     
        // Reset our known page widths; populate will recompute them.
        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
     
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            if (!lp.isDecor) {
     
                lp.widthFactor = 0.f;
            }
        }

        setCurrentItemInternal(newCurrItem, false, true);
        requestLayout();
    }
}

只关注中间那个for循环,看到两个if判断。

    if (newPos == PagerAdapter.POSITION_UNCHANGED) {
     
            continue;
        }
    if (newPos == PagerAdapter.POSITION_NONE) {
     
    			...
    			continue;
    }

看来找到了不会更新的原因了。newPos通过getItemPosition()方法赋值,而getItemPosition()默认返回POSITION_UNCHANGED,所以在判断中就不会往下执行别的代码逻辑了。

/**
     * Called when the host view is attempting to determine if an item's position
     * has changed. Returns {@link #POSITION_UNCHANGED} if the position of the given
     * item has not changed or {@link #POSITION_NONE} if the item is no longer present
     * in the adapter.
     */
public int getItemPosition(@NonNull Object object) {
     
    return POSITION_UNCHANGED;
}

既然发现了问题的原因,就可以想到解决的办法了。那就是重写getItemPosition()方法,重写之前先了解这个方法的作用。

看到官方的注释,翻译为在宿主视图试图确定项目的位置是否已更改时调用。如果给定项目的位置未更改,则返回{@link #POSITION_UNCHANGED};如果适配器中不再存在该项目,则返回{@link #POSITION_NONE}。

那么直接重写让getItemPosition()返回POSITION_NONE,再调用notifyDataSetChanged()方法。至此我的问题解决了。虽然效率低下,哈哈哈哈。

懒加载的一些问题

实现懒加载就是需要重写setUserVisibleHint(boolean isVisibleToUser)方法,当界面对用户可见时,再从网络加载数据,展示到界面上。

实现的方式都差不多一样的。我这里就不在重复了,网上搜搜很多博客和帖子。

至于懒加载需要注意的就是setUserVisibleHint(boolean isVisibleToUser)会调用很多次,一般都是在界面创建前调用,就是在onViewCreated()之前执行。

你可能感兴趣的:(android)