Reputation: 43524
We're suffering from a very strange issue with ViewPager here. We embed lists on each ViewPager page, and trigger notifyDataSetChanged both on the list adapter and the view pager adapter when updating list data.
What we observe is that sometimes, the page does not update its view tree, i.e. remains blank, or sometimes even disappears when paging to it. When paging back and forth a few times, the content will suddenly reappear. It seems as if Android is missing a view update here. I also noticed that when debugging with hierarchy viewer, selecting a view will always make it reappear, apparently because hierarchy viewer forces the selected view to redraw itself.
I could not make this work programmatically though; invalidating the list view, or the entire view pager even, had no effect.
This is with the compatibility-v4_r7 library. I also tried to use the latest revision, since it claims to fix many issues related to view pager, but it made matters even worse (for instance, gestures were broken so that it wouldn't let me page through all pages anymore sometimes.)
Is anyone else running into these issues, too, or do you have an idea of what could be causing this?
Upvotes: 92
Views: 55274
Reputation: 1
For Kotlin users:
In your fragments;
Use childFragmentManager
instead of viewPagerAdapter
Upvotes: -1
Reputation: 2780
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);
}
});
Upvotes: 3
Reputation: 1008
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).
Upvotes: 0
Reputation: 1544
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.
Upvotes: -1
Reputation: 650
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());
Upvotes: 59
Reputation: 3574
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();
}
Upvotes: 0
Reputation: 7703
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];
Upvotes: -1
Reputation: 12672
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!
Upvotes: 0
Reputation: 271
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);
}
Upvotes: 23
Reputation: 1876
We finally managed to find a solution. Apparently our implementation suffered of two issues:
destroyItem()
.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
.
Upvotes: 46
Reputation: 2538
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.
Upvotes: 0
Reputation: 8477
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):
New > Android Sample Project
Support4Demos
Android Tools > Add Support Library
Fragment
and then Pager
The code for this is in src/com.example.android.supportv4.app/FragmentPagerSupport.java
. Good luck!
Upvotes: 1
Reputation: 31871
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.
Upvotes: 11