In one of my apps, I need to add Fragments on both sides of the ViewPager. First of all, I will get a constant of 5 feeds, and my ViewPager will show feed at index 2 i.e. my current displayed Fragment will contain data present at index 2. So overall my ViewPager will show center of 5 feeds at start and that i have achieved by just setting the ViewPager current Item as 2 like this
viewPager.setCurrentItem(2);
Now user can swipe both sides, when he will swipe left from center position, I will look for next feed i.e fetch from server and add feed at zero index of my ViewPager like this
feedsList.add(0, modelClassObject); // feedsList will contain all feeds and has been used by adapter to show data in fragments.
adapter.notifyDataSetChanged();
and when i swipe right from center position, i will add feed at the last simply like this
feedsList.add(modelClassObject);
adapter.notifyDataSetChanged();
Now the problem is if i only add feeds at the right i.e at the end of the feedsList, everything works fine, but problem comes when i add feeds at zero index. My adapter is not showing that new feed that has been added to zero position instead it is repeating one of the existing feed and that too on the right side but not on the left. I Have tried everything, but nothing is going right way. Here is my adapter code.
private class HorizontalPagerAdapter extends FragmentStatePagerAdapter {
public HorizontalPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int arg0) {
return FeedUserProfileFragment.newInstance(feedsList.get(arg0),
arg0);
}
#Override
public int getCount() {
return feedsList.size();
}
}
I have also used this
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
but no results.. :(
So in severe need, If anyone had done that earlier and faced the same issue, please let me know what i am doing wrong. I only need to add fragment at zero index of my ViewPager.
I faced a similar problem before, and my solution was :
at first the list is declared in the adapter itself, so that when creating an instance of that adapter I can have it's own list then.
modified the method getItem(int arg0) in the adapter class so that it returns a specific item from the list depending on that item position.
when creating a new fragment, use instantiate method to create it, and after that add it to your fragments.
So, the complete solution would be :
adapter class:
private class HorizontalPagerAdapter extends FragmentStatePagerAdapter {
public List<Fragment> feedsList;
public HorizontalPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return feedsList.get(position);
}
#Override
public int getCount() {
return feedsList.size();
}
}
and when creating the adapter:
public static YourPageAdapter adapter_obj; // make sure it's static object
adapter_obj = new YourPageAdapter(getSupportFragmentManager());
adapter_obj.feedsList = new ArrayList<Fragment>();
// then add the list of fixed fragments to it(the 5 in the beginning)
adapter_obj.feedsList = fragments_list; // an ArrayList contains the 5 fragments
and when want to create a new fragment:
adapter_obj.feedsList.add(0, Fragment.instantiate(mContext, ViewPager_Container_Class.class.getName(), page));
adapter_obj.notifyDataSetChanged();
FragmentStatePagerAdapter can't handle it right when you add a new fragment in front.
The workaround is this:
Add a new fragment at the end.
Call notifyDataSetChanged();
Bring the fragment to front.
Call notifyDataSetChanged();
BTW, getItemPosition() should return correct positions all along:
public int getItemPosition(Object object)
{
return feedsList.indexOf( object );
}
So, with your code, it should be:
newFrag = Fragment.instantiate(...);
feedsList.add( newFrag );
adapter_obj.notifyDataSetChanged();
feedsList.remove( feedsList.size() - 1 );
feedsList.add( 0, newFrag );
adapter_obj.notifyDataSetChanged();
I guess the implementation of FragmentStatePagerAdapter doesn't expect both adding a new fragment and changing position happen at the same time.
Related
I have a ViewPager whose getCount(); changes based on a list of objects passed through the constructor. Using the adapter.notifyDataSetChanged(); I expect to see another page added. This seems to work correctly but when I remove an item from the mentioned list (used to calculate the getCount()) the ViewPager only removes the page successfully if its outside the offsetLimit.
Exemple: I have two pages and through a button I remove an item from the list. Then I call notifyDataSetChanged() and expect to have only one page left, but... the second "Page" is still there even thought I cannot swipe to it!!!!
How can I come around this? I want to avoid re-instantiating the whole ViewPager with the fragments.
CODE (simplification)
public class Adapter extends FragmentStatePagerAdapter {
private final List<String> list;
public Adapter(FragmentManager fragmentManager, List<String> list) {
super(fragmentManager);
this.list = list;
}
#Override
public int getCount() {
return list.size();
}
}
And this is inside the Activity:
adapter = new Adapter(getSupportFragmentManager(), items);
final ViewPager pager = findViewById(R.id.pager);
pager.setAdapter(adapter);
findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
items.add("test");
adapter.notifyDataSetChanged();
}
});
findViewById(R.id.remove).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
items.remove(items.size() - 1);
adapter.notifyDataSetChanged();
}
});
With this I can add items from the Activity and update de adapter (it works) and remove ... its doesn't work. What can I do?
You can override getItemPosition() & return POSITION_NONE in it.
getItemPosition() :
Called when the host view is attempting to determine if an item's position has changed. Returns POSITION_UNCHANGED if the position of the given item has not changed or POSITION_NONE if the item is no longer present in the adapter.
The default implementation assumes that items will never change position and always returns POSITION_UNCHANGED.
I will try to explain what I am thinking of doing as an Android app. But I am really confused how would I approach this problem.
When you get JSON from some web API (eg Location) and let's say that JSON has 5 different locations and you want to store each Location as a separate list item in a list view. This is simple, you use a location adapter class, and then those 5 items get stored as a list. For example, JSON updates 24h later and now there are 10 locations. No problems at all - Android handles this because of location adapter and etc. (I know all of this). Basically, what I am trying to tell that android does not need to know how many list items there will be before fetching information from JSON.
Now, the problem is that I am trying to create a swipe views which will represent each of the list items (1 Full view = 1 list item). For example, if there are 5 locations, i can only swipe 4 times and then I will reach the last tab. If there is update, and there are 10 locations, I could only swipe 9 times until I reach the end. I hope you understand idea.
My question is - how do I create dynamic swipe views where each of the list items would have its own separate window and to reach another list item you would swipe?
My main concern is how do you not tell android how many swipe views you will need and he would figure it out when he reads the JSON and knows the number of locations.
Many Thanks
Let's say your data is like this:
{"India","Morocco","China","Russia"}
You can getLength of the JSON object.In this case it is 4.Save that in a static variable.Suppose
max_swipes=4
Then in you swipe method
`if(position<=max_swipes || position==0){//code to swipe }
else
{
//cannot swipe last position
}`
To implement such functionality you just simply can use viewPager. You can copy the code from here and here. These are two file and you just need to copy as it is. After adding these two files in your project you need to create an adapter and here is the thing which makes it dynamic to create swipe views.
I am adding code snippet hope it will help you.
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
private DetailFragment page;
CharSequence Titles[]; // This will Store the Titles of the Tabs which are Going to be passed when ViewPagerAdapter is created
int NumbOfTabs; // Store the number of tabs, this will also be passed when the ViewPagerAdapter is created
// Build a Constructor and assign the passed Values to appropriate values in the class
public ViewPagerAdapter(FragmentManager fm, CharSequence mTitles[], int mNumbOfTabsumb) {
super(fm);
this.Titles = mTitles;
this.NumbOfTabs = mNumbOfTabsumb;
}
//This method return the fragment for the every position in the View Pager, This method is called only when we slide or change the tabs and not called upon rotating the screen
#Override
public Fragment getItem(int position) {
if(position < NumbOfTabs)
{
page= new DetailFragment();
return page;
}else {
return null;
}
}
// This method return the titles for the Tabs in the Tab Strip(in case you want to add title to each page.
#Override
public CharSequence getPageTitle(int position) {
return Titles[position];
}
#Override
public int getCount() {
return NumbOfTabs;
}
}
While creating the instance of this adapter you can pass the number of page you are going to require by calculating the number of items in JSON.
Hope this will help.
I asked this question over a year ago and got some good feedback about how to implement it but that project was abandoned. It has now resurfaced and I'm rekindling this fire.
The closest answer I got is here: Android ViewPager: Move any page to end programatically?
Essentially what I'm trying to do is reorder view fragments (and add new view fragments) based on data that I retrieve from a web service. The solution I posted the link to above works with a FragmentPagerAdapter but does not appear to work with a FragmentStatePagerAdapter. When I call notifyDataSetChanged() after rebuilding my data set, when using FragmentStatePagerAdapter, I get outofbounds exceptions because the size of my Array is not large enough. If I change everything up to use FragmentPagerAdapter, I do not get that exception, but I also am not seeing the fragments ordered correctly.
here is my fragmentstatepageradapter:
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
public int getItemId(int position) {
//
return Entries.contestEntries.get(position).entry_id;
}
#Override
public int getItemPosition(Object object) {
android.support.v4.app.Fragment f = (android.support.v4.app.Fragment)object;
for(int i = 0; i < getCount(); i++){
android.support.v4.app.Fragment fragment = getItem(i);
if(f.equals(fragment)){
return i;
}
}
return POSITION_NONE;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
int entryId = getItemId(position);
if(Entries.mItems.get(entryId) != null) {
return Entries.mItems.get(entryId);
}
Fragment f = ScreenSlidePageFragment.create(position);
Entries.mItems.put(entryId, f);
return f;
}
#Override
public int getCount() {
return Entries.contestEntries.size();
}
}
as you can see, I'm just referencing static properties on my activity (Entries) because when I tried pushing those into members of the adapter, it wouldn't work at all.
here are the relevant properties from that activity:
public static ViewPager mPager;
public static PagerAdapter mPagerAdapter;
public static HashMap<Integer, android.support.v4.app.Fragment> mItems = new HashMap<Integer, android.support.v4.app.Fragment>();
public static int CurrentEntryId;
public static ArrayList<ContestEntryModel> contestEntries;
When the activity receives a new order, I'm currently just doing an mItems.clear() before I call notifyDataSetChanged(). This works fine but I have two issues with it: 1) it causes the screen to flash as it rebuilds the current fragment and 2) this is using more resources that I probably need to be using as I should be able to just use fragments that have already been built.
the way I'm resetting contestEntries is by receiving a JSON string from the web service then gson'ing it to my model, then resetting the contestEntries ArrayList to that new value.
I've also tried reordering the HashMap before calling notifyDataSetChanged() and that didn't work.
I have no problems changing this solution to use FragmentPagerAdapter() from FragmentStatePagerAdapter() if that's what's required to make this work, but as I said above, in the testing I did yesterday, it was not reordering the fragments using the solution posted above.
any suggestions would be greatly appreciated.
TIA
I'm using a FragmentStatePagerAdapter with a ViewPager.
Everything is working fine. If I open my activity with an empty Adapter, the ViewPageris empty, if I add items, the ViewPager updates correctly.
BUT, if I open my activity and delete the last item of my ViewPager, the ViewPagerdoes not invalidate correctly and keeps the last Fragment visible.
How can I avoid this?
I'm using my library, it's a wrapper class for ViewPager + ViewPagerIndicator + FragmentPager(State)Adapter:
The class itself is placed here: https://github.com/MichaelFlisar/PagerManager/blob/master/src/com/michaelflisar/pagermanager/MFragmentPagerStateAdapter.java
The implementation is placed here: https://github.com/MichaelFlisar/PagerManager/blob/master/src/com/michaelflisar/pagermanager/MPagerAdapterHelper.java
It implements a simple FragmentStatePagerAdapter with weak references to it's fragments...
My code looks like following:
mPagerManager = new MPagerManager<ExerciseViewFragment, MFragmentPagerStateAdapter<ExerciseViewFragment>>(pager, tpi,
new MFragmentPagerStateAdapter<ExerciseViewFragment>(fragmentManager)
{
#Override
public CharSequence getPageTitle(int pos)
{
return mData.workout.getWExercise().get(pos).getExercise().getName();
}
#Override
public int getCount()
{
return mData.workout.getWExercise().size();
}
#Override
public ExerciseViewFragment createFragment(int pos)
{
return ExerciseViewFragment.newInstance(pos, mData.workout.getWExercise());
}
});
I'm calling mPagerManager.notifyDataSetChanged(); which forwards the call to the FragmentPagerStateAdapter directly...
PS: I know, I can make it invisible, if item count is 0... But I'm wondering if there's a better solution
This is an old question but I thought you might still need to know what to do. It's very common issue. ViewPager does not invalid views which are already created(including these which are ready on the left and right side of your current view).
Solving this is very easy. Just implement the following method in your adapter like this:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
By default this method returns PagerAdapter.POSITION_UNCHANGED.
For most developers this method usage is misleading, I had this problem myself till I realised that this method is used by ViewPager.dataSetChanged() to establish which items should be recreated. With above code you tell ViewPager to recreate all items whenever data set change.
I have a ViewPager set up that is drawing the data for its pages (views) from data passed down from a server. On occasion, the server will send down new data that will re-order the views (and sometimes add new views) and I need to make that happen seamlessly on my android device while the user is currently viewing a particular fragment.
I have it set to call notifyDataSetChanged() when the new data is pulled down from the server, but that seems to keep a cached version of the slides to the left and right of the currently viewed slide which potentially will change with the reorder. I have looked at this topic here: ViewPager PagerAdapter not updating the View and implemented the first solution which works fine, other than it reloads the current slide that is being viewed which won't work for my purposes. I took a shallow-dive look into implementing the setTag() method proposed on that same page in the second answer and that would work for updating information on the slides, but it won't help me for reordering them.
is there some way I can reorder all of the slides behind the scenes w/o causing any burps in the currently viewed slide?
TIA
EDIT: adding code and asking for some further clarification
This is my Adapter class.
private class ScreenSlidePagerAdapter extends FragmentPagerAdapter {
ContestEntriesModel entries;
public ScreenSlidePagerAdapter(FragmentManager fm, ContestEntriesModel Entries) {
super(fm);
this.entries = Entries;
}
#Override
public long getItemId(int position) {
return entries.entries[position].ContestEntryId;
}
#Override
public int getItemPosition(Object object) {
android.support.v4.app.Fragment f = (android.support.v4.app.Fragment)object;
for(int i = 0; i < getCount(); i++){
android.support.v4.app.Fragment fragment = getItem(i);
if(f.equals(fragment)){
return i;
}
}
return POSITION_NONE;
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
long entryId = getItemId(position);
//Log.d("EntryIds",Integer.toString(entryId));
if(mItems.get(entryId) != null) {
return mItems.get(entryId);
}
Fragment f = ScreenSlidePageFragment.create(position);
mItems.put(entryId, f);
return f;
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
And here is what I'm using when a new payload comes down from the server:
#Override
protected void onPostExecute(ContestEntriesModel entries) {
Fragment currentFragment = ContestEntries.mItems.get(ContestEntries.CurrentEntryId);
Log.d("fromUpdate",Long.toString(ContestEntries.CurrentEntryId));
ContestEntries.Entries = entries;
ContestEntries.NUM_PAGES = ContestEntries.Entries.entries.length;
ContestEntries.mPagerAdapter.notifyDataSetChanged();
ContestEntries.mPager.setCurrentItem(ContestEntries.mPagerAdapter.getItemPosition(currentFragment));
}
Check my answer here. The key is to override both getItemId(int) and getItemPosition(Object) in your adapter.
getItemId(int) is used to identify your pages uniquely within your dataset. getItemPosition(Object) is used to fetch the (updated) position for this unique page in your dataset.
If you want to keep focus on the same page before and after updating (adding/removing pages), you should call viewpager.setCurrentItem(int) for the new position of your page after calling .notifyDataSetChanged() on your adapter.