mxk
mxk

Reputation: 43524

ViewPager does not redraw content, remains/turns blank

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

Answers (13)

For Kotlin users:

In your fragments; Use childFragmentManager instead of viewPagerAdapter

Upvotes: -1

Zeero0
Zeero0

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

dfinn
dfinn

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

Meanman
Meanman

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

Naiad
Naiad

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

Chris Sprague
Chris Sprague

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

Roman Nazarevych
Roman Nazarevych

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

Oleg Vaskevich
Oleg Vaskevich

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

Ringen
Ringen

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

futtetennista
futtetennista

Reputation: 1876

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.

Upvotes: 46

Tim O'Brien
Tim O'Brien

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

Sparky
Sparky

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):

  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!

Upvotes: 1

Chris Banes
Chris Banes

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

Related Questions