I'm using the ViewPager from the compatibility library. I have succussfully got it displaying several views which I can page through.
However, I'm having a hard time figuring out how to update the ViewPager with a new set of Views.
I've tried all sorts of things like calling mAdapter.notifyDataSetChanged()
, mViewPager.invalidate()
even creating a brand new adapter each time I want to use a new List of data.
Nothing has helped, the textviews remain unchanged from the original data.
Update: I made a little test project and I've almost been able to update the views. I'll paste the class below.
What doesn't appear to update however is the 2nd view, the 'B' remains, it should display 'Y' after pressing the update button.
public class ViewPagerBugActivity extends Activity {
private ViewPager myViewPager;
private List<String> data;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
data = new ArrayList<String>();
data.add("A");
data.add("B");
data.add("C");
myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
myViewPager.setAdapter(new MyViewPagerAdapter(this, data));
Button updateButton = (Button) findViewById(R.id.update_button);
updateButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
updateViewPager();
}
});
}
private void updateViewPager() {
data.clear();
data.add("X");
data.add("Y");
data.add("Z");
myViewPager.getAdapter().notifyDataSetChanged();
}
private class MyViewPagerAdapter extends PagerAdapter {
private List<String> data;
private Context ctx;
public MyViewPagerAdapter(Context ctx, List<String> data) {
this.ctx = ctx;
this.data = data;
}
@Override
public int getCount() {
return data.size();
}
@Override
public Object instantiateItem(View collection, int position) {
TextView view = new TextView(ctx);
view.setText(data.get(position));
((ViewPager)collection).addView(view);
return view;
}
@Override
public void destroyItem(View collection, int position, Object view) {
((ViewPager) collection).removeView((View) view);
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
}
@Override
public void startUpdate(View arg0) {
}
@Override
public void finishUpdate(View arg0) {
}
}
}
I found very interesting decision of this problem. Instead of using FragmentPagerAdapter, which keep in memory all fragments, we can use FragmentStatePagerAdapter (android.support.v4.app.FragmentStatePagerAdapter), that reload fragment each time, when we select it.
Realisations of both adapters are identical. So, we need just change "extend FragmentPagerAdapter" on "extend FragmentStatePagerAdapter"
what worked for me was going
viewPager.getAdapter().notifyDataSetChanged();
and in the adapter putting your code for updating the view inside
getItemPosition
like somight not be the most correct way of going about it but it worked (the
return POSITION_NONE
trick caused a crash for me so wasnt an option)Change the
FragmentPagerAdapter
toFragmentStatePagerAdapter
.Override
getItemPosition()
method and returnPOSITION_NONE
.Eventually, it will listen to the
notifyDataSetChanged()
on view pager.After a lot of searching for this problem, I found a really good solution that I think is the right way to go about this. Essentially, instantiateItem only gets called when the view is instantiated and never again unless the view is destroyed (this is what happens when you override the getItemPosition function to return POSITION_NONE). Instead, what you want to do is save the created views and either update them in the adapter, generate a get function so someone else can update it, or a set function which updates the adapter (my favorite).
So, in your MyViewPagerAdapter add a variable like:
an in your instantiateItem:
so, this way, you can create a function that will update your view:
Hope this helps!
The code below worked for me.
Create a class which extends the FragmentPagerAdapter class as below.
Then inside each Fragment you created, create an updateFragment method. In this method you change the things you need to change in the fragment. For example in my case, Fragment0 contained a GLSurfaceView which displays a 3d object based on a path to a .ply file, so inside my updateFragment method I change the path to this ply file.
then create a ViewPager instance,
and an Adpater instance,
then do this,
Then inside the class were you initialized the Adapter class above and created a viewPager, every time you want to update one of your fragments (in our case Fragment0) use the following:
This solution was based on the technique suggested by Alvaro Luis Bustamante.
I don't think there is any kind of bug in the
PagerAdapter
. The problem is that understanding how it works is a little complex. Looking at the solutions explained here, there is a misunderstanding and therefore a poor usage of instantiated views from my point of view.The last few days I have been working with
PagerAdapter
andViewPager
, and I found the following:The
notifyDataSetChanged()
method on thePagerAdapter
will only notify theViewPager
that the underlying pages have changed. For example, if you have created/deleted pages dynamically (adding or removing items from your list) theViewPager
should take care of that. In this case I think that theViewPager
determines if a new view should be deleted or instantiated using thegetItemPosition()
andgetCount()
methods.I think that
ViewPager
, after anotifyDataSetChanged()
call takes it's child views and checks their position with thegetItemPosition()
. If for a child view this method returnsPOSITION_NONE
, theViewPager
understands that the view has been deleted, calling thedestroyItem()
, and removing this view.In this way, overriding
getItemPosition()
to always returnPOSITION_NONE
is completely wrong if you only want to update the content of the pages, because the previously created views will be destroyed and new ones will be created every time you callnotifyDatasetChanged()
. It may seem to be not so wrong just for a fewTextView
s, but when you have complex views, like ListViews populated from a database, this can be a real problem and a waste of resources.So there are several approaches to efficiently change the content of a view without having to remove and instantiate the view again. It depends on the problem you want to solve. My approach is to use the
setTag()
method for any instantiated view in theinstantiateItem()
method. So when you want to change the data or invalidate the view that you need, you can call thefindViewWithTag()
method on theViewPager
to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.Imagine for example that you have 100 pages with 100
TextView
s and you only want to update one value periodically. With the approaches explained before, this means you are removing and instantiating 100TextView
s on each update. It does not make sense...