ViewPager 不重绘内容,保持/变为空白

我们正在遭受一个非常奇怪的问题与视频寻呼机在这里。我们在每个 ViewPager 页面上嵌入列表,并在更新列表数据时在列表适配器和视图页导航适配器上触发 notifyDataSetChanged。

我们观察到的是,有时,页面不更新其视图树,即保持空白,有时甚至在分页到它时消失。当来回翻页几次时,内容会突然重新出现。似乎 Android 在这里缺少一个视图更新。我还注意到,当使用层次结构查看器进行调试时,选择一个视图总是会让它重新出现,这显然是因为层次结构查看器强制选中的视图重新绘制自己。

但是,我无法以编程方式实现这一点; 使列表视图或整个视图页导航失效没有任何效果。

这与兼容性 -v4 _ r7库是一致的。我还尝试使用最新的修订版,因为它声称可以修复许多与查看页面相关的问题,但它使事情变得更糟(例如,手势被破坏,以至于有时它不再让我浏览所有页面)

还有其他人也遇到这些问题吗,或者你知道是什么引起的吗?

54176 次浏览

ViewPager tries to do clever stuff around re-using items, but it requires you to return new item positions when things have changed. Try adding this to your PagerAdapter:

public int getItemPosition (Object object) { return POSITION_NONE; }

It basically tells ViewPager that everything has changed (and forces it to re-instantiate everything). That's the only thing I can think of off the top of my head.

The Android Support Library has a demo Activity that includes a ViewPager with a ListView on every page. You should probably have a look and see what it does.

In Eclipse (with Android Dev Tools r20):

  1. Select New > Android Sample Project
  2. Select your target API level (I suggest the newest available)
  3. Select Support4Demos
  4. Right-click the project and select Android Tools > Add Support Library
  5. Run the app and select Fragment and then Pager

The code for this is in src/com.example.android.supportv4.app/FragmentPagerSupport.java. Good luck!

I ran into this and had very similar issues. I even asked it on stack overflow.

For me, in the parent of the parent of my view someone subclassed LinearLayout and overrode requestLayout() without calling super.requestLayout(). This prevented onMeasure and onLayout from being called on my ViewPager (although hierarchyviewer manually calls these). Without being measured they'll show up as blank in ViewPager.

So check your containing views. Make sure they subclass from View and don't blindly override requestLayout or anything similar.

We finally managed to find a solution. Apparently our implementation suffered of two issues:

  1. our adapter did not remove the view in destroyItem().
  2. we were caching views so that we'd have to inflate our layout just once, and, since we were not removing the view in destroyItem(), we were not adding it in instantiateItem() but just returning the cached view corresponding to the current position.

I haven't looked too deeply in the source code of the ViewPager - and it's not exactly explicit that you have to do that - but the docs says :

destroyItem()
Remove a page for the given position. The adapter is responsible for removing the view from its container, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup).

and:

A very simple PagerAdapter may choose to use the page Views themselves as key objects, returning them from instantiateItem(ViewGroup, int) after creation and adding them to the parent ViewGroup. A matching destroyItem(ViewGroup, int, Object) implementation would remove the View from the parent ViewGroup and isViewFromObject(View, Object) could be implemented as return view == object;.

So my conclusion is that ViewPager relies on its underlying adapter to explicitly add/remove its children in instantiateItem()/destroyItem(). That is, if your adapter is a subclass of PagerAdapter, your subclass must implement this logic.

Side note: be aware of this if you use lists inside ViewPager.

I had the exact same problem but I actually destroyed the view in destroyItem (I thought). The problem however was that I destroyed it using viewPager.removeViewAt(index); insted of viewPager.removeView((View) object);

Wrong:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
viewPager.removeViewAt(position);
}

Right:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
viewPager.removeView((View) object);
}

Had the same issue, which is something to do with ListView (because my empty view shows up fine if the list is empty). I just called requestLayout() on the problematic ListView. Now it draws fine!

I had similar issue. I cache views because I need only 3 views in ViewPager. When I slide forward everything is okay but when I start to slide backward occurs error, it says that "my view already has a parent". The solution is to delete unneeded items manually.

@Override
public Object instantiateItem(ViewGroup container, int position) {
int localPos = position % SIZE;
TouchImageView view;
if (touchImageViews[localPos] != null) {
view = touchImageViews[localPos];
} else {
view = new TouchImageView(container.getContext());
view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
touchImageViews[localPos] = view;
}
view.setImageDrawable(mDataModel.getPhoto(position));
Log.i(IRViewPagerAdpt.class.toString(), "Add view " + view.toString() + " at pos: " + position + " " + localPos);
if (view.getParent() == null) {
((ViewPager) container).addView(view);
}
return view;
}


@Override
public void destroyItem(ViewGroup container, int position, Object view) {
//      ((ViewPager) container).removeView((View) view);
Log.i(IRViewPagerAdpt.class.toString(), "remove view " + view.toString() + " at pos: " + position);
}


..................


private static final int SIZE = 3;
private TouchImageView[] touchImageViews = new TouchImageView[SIZE];

I ran into this same problem when using a ViewPager and FragmentStatePagerAdapter. I tried using a handler with a 3 second delay to call invalidate() and requestLayout() but it didn't work. What did work was resetting the viewPager's background color as follows:

MyFragment.java

    private Handler mHandler;
private Runnable mBugUpdater;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = new ViewPager(getActivity());
//...Create your adapter and set it here...


mHandler = new Handler();
mBugUpdater = new Runnable(){
@Override
public void run() {
mVp.setBackgroundColor(mItem.getBackgroundColor());
mHandler = null;
mBugUpdater = null;
}
};
mHandler.postDelayed(mBugUpdater,50);


return rootView;
}


@Override
public void onPause() {
if(mHandler != null){
//Remove the callback if it hasn't triggered yet
mHandler.removeCallbacks(mBugUpdater);
mHandler = null;
mBugUpdater = null;
}
super.onPause();
}

If the ViewPager is set inside a Fragment with a FragmentPagerAdapter, use getChildFragmentManager() instead of getSupportFragmentManager() as the parameter to initialize your FragmentPagerAdapter.

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Instead of

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());

For me the problem was coming back to the activity after the app process was killed. I am using a custom view pager adapter modified from the Android sources.The view pager is embedded directly in the activity.

Calling viewPager.setCurrentItem(position, true);

(with animation) after setting the data and notifyDataSetChanged() seems to work, but if the parameter is set to false it doesn't and the fragment is blank. This is an edge case which may be of help to someone.

I had a problem with the same symptoms, but a different cause that turned out to be a silly mistake on my part. Thought I'd add it here in case it helps anyone.

I had a ViewPager using FragmentStatePagerAdapter which used to have two fragments, but I later added a third. However, I forgot that the default off screen page limit is 1 -- so, when I'd switch to the new third fragment, the first one would get destroyed, then recreated after switching back. The problem was that my activity was in charge of notifying these fragments to initialize their UI state. This happened to work when the activity and fragment lifecycles were the same, but to fix it I had to change the fragments to initialize their own UI during their startup lifecycle. In the end I also wound up changing setOffscreenPageLimit to 2 so that all three fragments were kept alive at all times (safe in this case since they were not very memory intensive).

Tried too many solutions but unexpectedly viewPager.post() worked

 mAdapter = new NewsVPAdapter(getContext(), articles);
viewPager.post(new Runnable() {
@Override
public void run() {
viewPager.setAdapter(mAdapter);
}
});

For Kotlin users:

In your fragments; Use childFragmentManager instead of viewPagerAdapter