I am trying to create an app using Viewpager2 that allows scrolling between fragments similar to below image but WITHOUT the tablayout at the top.
So if you scroll left and right you would swtich between the fragments. Thats fine and dandy.
However my final app would have more than 5 tabs so i dont want the tab layout listed on top. Instead I would have a menu option that would let user choose which fragment to view.
I cannot figure out how to get the viewpager to go to a specific fragment directly (eg. from 2nd fragment to the 5th fragment), and when navigating between fragments I do not want a fragment to start from scratch if if was created already.
I am using FragmentStateAdapter with my ViewPager2. Should i be using another adapter for this? Any ideas woule be greatful.
The easiest way to implement this feature is to define your ViewPager as static in your activity (so you can access it from anywhere in your application, including fragments). You can make it public, or you can make it private and create a getter for it.
private static ViewPager mViewPager; // This is the global variable
public static ViewPager getViewPager() { return mViewpager; } // This is the getter
Now lets say we have an activity MainActivity which contains the ViewPager. Each fragment inside this ViewPager contains a button which should control the page (doesn't have to be in the ViewPager, could be anywhere as long as it has a reference to your Activity and that Activity is still running, so don't start another one).
ViewPager contains the method
setCurrentItem(int item, boolean smoothScroll)
(smoothScroll is optional and is just for animation purposes)
Now you can do:
MainActivity.getViewPager().setCurrentItem(itemNr);
Or if you want a fancy scroll animation:
MainActivity.getViewPager().setCurrentItem(itemNr, true);
Now your second question / issue was that you mentioned you don't want fragments to get recreated. In that case I think it is better to use FragmentPagerAdapter, which maintains your fragments. You can call the following method to set a limit to these pages:
mViewPager.setOffscreenPageLimit(LauncherViewPageAdapter.NUMBER_OF_PAGES); //NUMBER_OF_PAGES was just a static constant in my LauncherViewPageAdapter class which extended ViewPageAdapter.
Note: Default this limit is 3, but you really don't have to worry about performance. I implemented 4 pages / fragments (widgets, tiles, app list, application settings) without any memory issues (even on older devices). Just don't put them full with bitmaps / heavy memory usage stuff.
I'm currently working on a launcher application which also uses ViewPager, so feel free to reply with a question about the ViewPager, since I solved the basic issues I had.
You can make use of setCurrentItem method of your ViewPager2. Just pass the index your wish to navigate to starting from 0. As for not wanting the fragments to start from scratch simply use setOffscreenPageLimit method of viewpager2 and set it to like 4 in case you have 5 pages so that all 5 pages stay in memory. It actually defeats the purpose of FragmentStateAdapter and ViewPager2 though.
Related
I know this question has been asked many times before but I couldn't find an explanatory answer.
I want to have a single MyFragment.java and display it for all 3 pages of ViewPager.
But when I try to create a fragment with this method:
new MyFragment()
and add it to the ViewPager I get this error:
java.lang.IllegalStateException: Fragment already added
at androidx.fragment.app.Fragment.setInitialSavedState(Fragment.java:679)
at androidx.viewpager2.adapter.FragmentStateAdapter.ensureFragment(FragmentStateAdapter.java:269)
at androidx.viewpager2.adapter.FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:175)
at androidx.viewpager2.adapter.FragmentStateAdapter.onBindViewHolder(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:7065)
at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:7107)
I'd like to know if there is any possibility to add the same fragment to ViewPager.
Possible applications of it include
A web browser having WebView fragments in Viewpager
Why exactly do we need to use singleton fragment?
Why can't we treat fragments as recycler items which can be multiple instantiated?
Even the viewpager2 is becoming more like RecyclerView.
If it is impossible to instantiate fragments repeatedly and add them
to the same ViewPager then are there any other ways except replacing
fragments in ViewPager adapter with normal inflated views? Would that
be a tolerable solution?
You get this error because when you add the fragment you don't put a distinct tag on it, so it checks whether the fragment is already added by its class. And the viewpager stores some pages in memory, so the previous fragment even though is no longer in view, is still in the memory.
When you add the fragment, give it a distinct tag
I have an Android Application implementing a PageView with Tabs found here: https://github.com/sbpc/ajavacc The PageView loads its Fragments and displays everything properly; but occasionally the individual Fragments become disposed. Is there a way to either store the Fragment's state, prevent the pages from being disposed; or do I need to save the values which construct each page to a file?
The class MainPagerAdapter is already using FragmentPagerAdapter which keeps the created Fragments in memory. You can go one step further and add the method setRetainInstance(true) to each Fragment in the ViewPager. This should work. You can read more about this here.
The ViewPager object is implemented such that the next screen is already created in memory before the user swipes to it. ViewPager has a method setOffscreenPageLimit(n) in which you can set the number of ViewPager fragments that are created in advance, but n=0 is not allowed. The reason behind that is to garantee a 'smooth user experience'. In my case however then content of page n+1 is determined by what the user has done on page n. For instance if the user has clicked on a checkbox on page n, it can happen that some widget should not be shown on page n+1. My question is: how can I ensure that page n+1 is recreated? If that goes at the cost of a 'smooth user experience' so be it. I am able to intercept the swipe event in:
pager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener()
In that method I can call a refresh() method of the current fragment. The problem is: what do I do in that refresh() method or is this the wrong approach? The user interface I want to recreate is in the onCreate method, a callback method. Or can't this work and do I need to replace the Fragment (n+1) in memory with a new one and if so, how do I do that?
Any help would be appreciated.
Trying to replace the fragment associated with a page in a ViewPager is going to be the responsibility of your fragment-based PagerAdapter. I am not aware of an easy way to accomplish that with FragmentPagerAdapter or FragmentStatePagerAdapter. My ArrayPagerAdapter can handle it better, though I don't offer a direct replace() operation.
In your case, the better solution is to adjust the fragment that you have, rather than to replace the fragment outright. In theory, this should be possible for just about any degree of change. In practice, there is probably a level of complexity after which trying to replace the fragment would be simpler than trying to have a mashup of all possible fragments.
Note that nested fragments might be another option, where the N+1th page was a placeholder and ran a FragmentTransaction to populate itself when shown. Nested fragments are tricky and quirky.
With regards to the "holes", View.INVISIBLE indicates that you want the widget to continue taking up space (e.g., so stuff after it in a LinearLayout does not move), but do not draw the pixels. View.GONE means that the View is totally ignored for layout purposes, though it is still in the hierarchy and so can be easily toggled back to View.VISIBLE if needed. Another possibility would be to remove the View entirely from the parent container, though this should have no visible change when compared with View.GONE.
I have created an android app that uses a ViewPager to swipe through three fragments (each of the three fragments contains a gridview).
I began learning about fragments recently and assumed that after my gridviews were created in their fragments, that each fragment would be in memory and never have to load again.
Recently I noticed that when I swiped from my left fragment, to the middle fragment, then to the right fragment, that the left-most fragment would have to have its gridview filled again with my adapter.
I was just wondering why this happens when I navigate from the left-most fragment to the right-most fragment, but not when I navigate between side-by-side fragments. Does it only keep the most recent fragment in memory and kill the other fragment? Or is there some other reason why an app wouldn't keep all three fragments in memory? My app gets a little laggy and slow when I quickly navigate between fragments, so it would be nice to only have to draw each gridview one time if possible.
I fill my arraylist (used to fill adapter) in onCreate(), and then fill my gridview with the adapter in onActivityCreated(Bundle savedInstanceState)
The ViewPager keeps a certain amount of off-screen tabs (Fragments, of course) in memory. The default, I believe for all devices, is 1. Thus, when you scroll to the far right, only the one to its left will be kept in memory.
If you want your ViewPager to retain all tabs in memory (careful--this can be hard on the device or other running apps), you can set how many off-screen tabs to keep in memory. To do this, use the setOffscreenPageLimit method of ViewPager on your ViewPager object.
Example:
ViewPager pager = (ViewPager) findViewById(R.id.pager); // Your pager's ID
pager.setOffscreenPageLimit(2); // Will retain 2 off-screen tabs + your selected tab
Hope this helps!
It's the nature of how a FragmentPagerAdapter and FragmentPagerStateAdapter work. From Google's site:
The fragment of each page the user visits will be kept in memory, though its view hierarchy may be destroyed when not visible.
Meaning, your GridView is refilling because your ViewPager killed the view and has to rebuild it.
EDIT: If you need to keep all three fragment Views in memory to speed things up, then you would have to create your own PagerAdapter that stores the views in instantiateItem(ViewGroup, int) inside an Collection of some sort.
Alternatively, if you are using Google's example verbatim, then you probably are doing something like this:
#Override
public Fragment getItem(int position) {
return ArrayListFragment.newInstance(position);
}
In which case, you're rebuilding a new fragment each time the ViewPager requests one.
By default ViewPager keeps in an idle state one fragment from the left and one from the right of the current page. You can change this behaviour with ViewPager.setOffscreenPageLimit(). See docs
In my application I am using ViewPager from the support library- v4
In main screen I have viewPager which got max 5 Fragment, all fragment belongs to one class ArticlePager
Now in main screen there are list on categories, now the content of the pager is based on that selection,
The problem I am having is, I have used FragmentPagerAdapterwhich stores the Fragment and if the fragment is already exist, It will return the old Fragment without recreating it. That things runs perfectly, but Problem Occurs # the time of orientation change.
For instance
If there are 5 View normally in every fragment For the given position, but There are also some which contains 2-3 views. Now if I change the orientation on page No. lets say 5 which contains only 3 view inside.
So, by now in every category on Page 5 I'll got the view containing 3 view, which is not something I want.
In my application each category contains the pagination
Is there any way such that i can destroy and recreate the Fragment on click of category? or any other work around
Thank you
OK thanks to open source I find my solution, FragmentPagerAdapter I have override the method instantiateItem and got the solution.
This can be easily achieved by FragmentStatePagerAdapter such that it doesn't store the fragment. It recreate it all the time, but I don't want that in 100's of page because of only few pages.
So if I understand correctly, your problem is that after rotation, the wrong set of fragments are in you ViewPager?
Why don't you check the current selected tab in onResume() or onStart() of your Activity, and create/assign a new PagerAdapter for you ViewPager with the correct fragments?