背景
最近在填前同事的一个坑时,不小心遇到另外一个坑。 在一个礼物面板,原实现是gridView + ViewPager实现的(有几页礼物),在送用户免费礼物时,刷新ViewPager里面的item时,出现了闪屏。
其实很多童鞋知道,PagerAdapter在调用notifyDataSetChanged(), 如果使用默认的会不起作用
点进notifyDataSetChanged()
/**
* This method should be called by the application if the data backing this adapter has changed
* and associated views should update.
*/
public void notifyDataSetChanged() {
synchronized (this) {
if (mViewPagerObserver != null) {
mViewPagerObserver.onChanged();
}
}
mObservable.notifyChanged();
}
可以看到
- mViewPagerObserver 是怎么传进来的呢?该类实际实现类是啥?
搜索全类只有一处赋值
void setViewPagerObserver(DataSetObserver observer) {
synchronized (this) {
mViewPagerObserver = observer;
}
}
可以看出是PagerObserver类,有ViewPager类初始化setAdapter(PagerAdapter adapter)的时候传过来。
回到刚才的 mViewPagerObserver.onChanged();PagerObserver的实现如下
@Override
public void onChanged() {
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();
}
}
这里的代码:
if (newPos == PagerAdapter.POSITION_UNCHANGED) {
continue;
}
if (newPos == PagerAdapter.POSITION_NONE) {
}
恩,明显是根据PagerAdapter.POSITION_NONE、PagerAdapter.POSITION_UNCHANGED来判断是否进行更新操作。 PagerAdapter.POSITION_UNCHANGED是什么时候打上标签的呢?
哎呀,getItemPosition方法返回的,于是有了解决方法1.
- mObservable.notifyChanged();
/**
* Invokes {@link DataSetObserver#onChanged} on each observer.
* 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) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
好吧这里是逐个通知Observer调用onChanged();
解决方案如下:
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
game over了么?当然没有。
上述解决方法只是解决了一个问题,注意测试的话,就会发觉引入了本文标题中提到的闪屏问题~~
到底是哪里出现的问题呢?前面的我们源码都读的没有问题,唯一没注意的就是最后更新的逻辑了。我们再次仔细看看:
注意标箭头的地方,原来这里是把整个item remove掉了,难怪会出现闪屏。 事实上我们也可以通过断点或打log的方式,看本文提到的gridView刷新时是否复用。
知道了这里,本文的解决方法如下,使用一个SparseArray来存储,然后手动刷新。
class MyPagerAdapter extends PagerAdapter {
private MyGridViewAdapter mGridAdapter;
private SparseArray<GridView> mViews = new SparseArray<>();
@Override
public int getCount() {
if (mInnerAdapter == null || mMaxRows == 0 || mColumns == 0) {
return 0;
}
return (int) Math.ceil(mInnerAdapter.getCount() / (double) (mMaxRows * mColumns));
}
// Remove a page for the given position.
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
mViews.remove(position);
}
// Determines whether a page View is associated with a specific key object as returned by instantiateItem(ViewGroup, int).
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
/**
* PagerAdapter.POSITION_NONE 会导致调用notifyDataSetChanged
* 调用 destroyItem 导致重新添加item,闪屏的出现
* 但是这里系统的实现bug, 见ViewPager$PagerObserver
* 默认是POSITION_UNCHANGED 即不刷新, 调用notifyDataSetChanged无反应,
* 这里使用手动刷新
*
* @param object
* @return
*/
@Override
public int getItemPosition(Object object) {
int index = -1;
if (mViews != null) {
index = mViews.indexOfValue((GridView) object);
}
return index != -1 ? index : PagerAdapter.POSITION_NONE;
}
@Override
public void notifyDataSetChanged() {
GridView view;
int size = mViews.size();
for (int index = 0; index < size; index++) {
view = mViews.valueAt(index);
if (view != null) {
((MyGridViewAdapter) view.getAdapter()).notifyDataSetChanged();
}
}
super.notifyDataSetChanged();
}
// Create the page for the given position.
@Override
public Object instantiateItem(ViewGroup container, int position) {
GridView mGridView = new GridView(mContext);
....
mGridAdapter = new MyGridViewAdapter(mInnerAdapter, position);
mGridView.setAdapter(mGridAdapter);
container.addView(mGridView);
mViews.put(position, mGridView);
return mGridView;
}
}