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
Related
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.
My Activity has a viewpager along with a tablayout. There are three tabs. Now while swiping between the tabs, there is noticeable delay (around 300ms) while i try to very quickly swipe from, say, the first to third tab.
Now the viewpager contains three fragments. I am using a FragmentPagerAdapter to declare the viewpager tabs (as this gave a slight improvement compared to when i used FragmentStatePagerAdapter). Also I have set offScreenPageLimit for the viewpager to 2 (since there are 3 tabs).
Now to find out what was going wrong, I initialised empty fragments for the tabs and the swiping was fine and smooth as expected.
Next, I implemented the full functional code for the middle/2nd tab and left the other two as empty fragment.
Now this is where things get interesting, I was expecting a small delay while i swipe into the middle fragment(it contains nothing but a listview), when it creates it views and plugs the adapter into the listview with data. However, that was not the case. There was a noticeable delay while swiping out of the middle fragment!
Thus I have reason to believe, that while swiping out, where the fragment's view is being removed from screen, there is something I need to take care of which will make the transition smoother.
I am trying to read RSS feed and show the content of it in card view which is inside ViewPager with tab layout. It shows fragment with data initially, but when swiped back from another tab, the whole card layout with data,disappears.
I had to handle the same situation. I had TabLayout in ViewPager.
I have used FragmentPagerAdapter with off screen page limit set to (NO_OF_TABS - 1). This is to hold all the fragments in memory and they wont be destroyed and recreated as you swipe back and forth. As my fragments are small in size meaning not many views every fragment has so it was okay for me. But this way fragments always holds the latest data. Say, if user had entered text in EditText and swipe to end and come back, then fragment will still have the text that user had entered.
In your case, if you have not set the page limit the default is 1 so the fragment gets destroyed when you swipe away so you lose the data.
IN ADDITION TO ABOVE:
If you can not keep all fragments in memory, you may consider using FragmentStatePagerAdapter. This adapter saves the states of fragments that are being destroyed and it will use the same state to represent in fragment when user swipe back to this fragment. But there are issues around StaePagerAdapter while it destroys adn recreate the fragments, there are indexing issues so you will easily get IndexOutOfBoundException if you swipe. This is with page screen limit set to less than the total fragments.
I have an Activity with ViewPager and three Fragments with-in. One Fragment contains ListView with custom CursorAdapter which loads data from database.
I've noticed that my cursor adapter loads data every time I switch Fragments in ViewPager. I think that it's normal and is due to the fact that every Fragment has its own lifecycle.
Regarding this it will be great pleasure for me if the users of the stackoverflow explain about their experience or best practices at all.
Thanks!
ViewPagers maintain the state of only certain number of fragments, which defaults to 1 for both sides of the current selected fragment. For example, if you have 3 fragments, when the first is selected, only first two fragments will be instantiated and have the listview data loaded. Alternatively, if you have the second fragment selected, the first and the third will be instantiated. If you switch to the third fragment, the second fragment will be retained, but the first one will be lost. However, you can set the number of fragments to be retained with calling setOffscreenPageLimit method on viewPager with any number of fragments to retain you need. Though you should remember, that setting the number too high may cause your app to consume too much memory.
For example, if you want your fragment to not reload listview content from db while switching fragments and you have 3 framents in your viewPager, you may write the following code:
ViewPager mViewPager;
mViewPager.setOffscreenPageLimit(2);
Say I have a viewpager with multiple fragments that hold bitmaps. I use setOffScreenLimit(numberOfPagesInViewPager) to make sure that the fragments are instantly available at any time (this is a requirement of my app). Quite, obviously the more fragments I have, the more memory I will use because of the bitmaps inside the fragments and fragments being kept in memory.
Now, suppose that those fragments all show the same content and therefore hold the same bitmaps.
Would it be possible to create only one fragment and reuse it in the viewpager for all pages (and hence reducing the memory footprint to 1 fragment)?
In code I am thinking about something like this:
public class MyAdapter extends FragmentPagerAdapter {
private MyFragment mFragment;
public MyAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
mFragment = MyFragment.newInstance();
}
#Override
public Fragment getItem(int position) {
// Reuse mFragment for all pages in the viewpager
return mFragment;
}
}
This approach does not work however, as I am getting this type of error:
java.lang.IllegalStateException: Can't change tag of fragment MyFragment{4259a4d0 id=0x7f0a0074 android:switcher:2131361908:0}: was android:switcher:2131361908:0 now android:switcher:2131361908:1
Would it be possible to create only one fragment and reuse it in the viewpager for all pages (and hence reducing the memory footprint to 1 fragment)?
Not with FragmentPagerAdapter and FragmentStatePagerAdapter. And I doubt that it would work even with your own custom PagerAdapter.
If you use an app that has a ViewPager, you will notice that the ViewPager animates when swiping, so you see parts of two pages at one time. That will be difficult with only one page. This is why ViewPager requires at least three pages at all times, so it can handle swipes in either direction.
Beyond that, I cannot quite understand why users will want to flip through a series of identical pages in a ViewPager.
Is your fragment complex? If it just has one imageview, you can implement a viewpager that uses simple layout view or just an image view as a base container.
The fragment adapter is frankly more useful if each of your fragments are of different structural code.
Not 100% sure, but I believe fragments get destroyed and re-created by view pager. You could double-check this in debugger. If this is true, then the fragment could be a thin wrapper around a bitmap, and bitmap itself could be cached instead of a fragment. There are multiple strategies on caching bitmaps as described at http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html.