Right now I'm developing an application, which basically uses this components for navigation and visualization:
- Navigation component,
- ViewPager,
- FragmentStatePagerAdapter,
- TabLayout.
In the application there is 7 fixed tab (fragment). Most of them have one RecyclerView and I fetch the data from a server.
I have two problem with this solution:
When I click an item in one of the RecyclerView, I use navigation component's navigate() method. Then I press back button, and the list is 'making a network call again' and scroll to the top (not retain the fragment's state).
When I swiping in the ViewPager it doesn't save the fragment's state and reloading them all the time. Yes, I'm familiar with the setOffscreenPageLimit() method, and it's working, but according to the docummentation: "You should keep this limit low, especially if your pages have complex layouts", so I shouldn't set it to 7.
So basically my question is, how Google do it on Play Store? Play Store has planty of tabs on the bottom navigation, plenty of tabs on the top, but somehow it seems to save all of my previous navigation history (RecyclerView's state etc.).
Thank you.
Adapter for ViewPager:
public class MainAdapter extends FragmentStatePagerAdapter {
private Context mCtx;
public MainAdapter(#NonNull FragmentManager fm, int behavior, Context ctx) {
super(fm, behavior);
mCtx = ctx;
}
#NonNull
#Override
public Fragment getItem(int i) {
return new NewsFragment();
}
#Override
public int getCount() {
return 7;
}
#Override
public CharSequence getPageTitle(int position) {
String tabTitle = "";
if (mCtx != null) {
switch (position) {
case 0:
tabTitle = mCtx.getString(R.string.title_fragment_news);
break;
case 1:
tabTitle = mCtx.getString(R.string.button_city_operation);
break;
case 2:
// TODO
tabTitle = "Tab 2";
break;
case 3:
// TODO
tabTitle = "Tab 3";
break;
case 4:
tabTitle = mCtx.getString(R.string.title_fragment_information);
break;
case 5:
tabTitle = mCtx.getString(R.string.title_fragment_announcement);
break;
case 6:
// TODO
tabTitle = "Tab7";
break;
}
}
return tabTitle;
}
}
Init viewpager and tablayout:
MainAdapter mMainPagerAdapter = new MainAdapter(getChildFragmentManager(), BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, getApplicationContext());
ViewPager mViewPager = view.findViewById(R.id.pager);
mViewPager.setAdapter(mMainPagerAdapter);
// mViewPager.setOffscreenPageLimit(3);
final TabLayout tabLayout = view.findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(mViewPager);
Related
I have Fragment connected with FragmentPagerAdapter, with code
public class LeaveAdapterApproval extends FragmentPagerAdapter{
private static final String TAG = LeaveAdapterApproval.class.getSimpleName();
private static final int FRAGMENT_COUNT = 4;
public LeaveAdapterApproval(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
Log.d(TAG, "LOG : CURRENT FRAGMENT LeaveFragmentToAll");
return new LeaveFragmentToAll();
case 1:
Log.d(TAG, "LOG : CURRENT FRAGMENT LeaveFragmentToPending");
return new LeaveFragmentToPending();
case 2:
Log.d(TAG, "LOG : CURRENT FRAGMENT LeaveFragmentToApproved");
return new LeaveFragmentToApproved();
case 3:
Log.d(TAG, "LOG : CURRENT FRAGMENT LeaveFragmentToDenied");
return new LeaveFragmentToDenied();
}
return null;
}
#Override
public int getCount() {
return FRAGMENT_COUNT;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position){
case 0:
return "All";
case 1:
return "Pending";
case 2:
return "Approved";
case 3:
return "Denied";
}
return null;
}
}
and results in Logcat
09-22 15:45:21.259 27234-27234/dan.taaku D/LeaveAdapterApproval: LOG : CURRENT FRAGMENT LeaveFragmentToPending
09-22 15:45:21.259 27234-27234/dan.taaku D/LeaveAdapterApproval: LOG : CURRENT FRAGMENT LeaveFragmentToAll
09-22 15:45:21.259 27234-27234/dan.taaku D/LeaveAdapterApproval: LOG : CURRENT FRAGMENT LeaveFragmentToApproved
We can see from the Logcat results that Fragment loads three Fragments at once, I set when the Fragment is opened Automatic to LeaveFragmentToPending in LeaveClassApproval
public class LeaveClassApproval extends Fragment {
private static final String TAG = LeaveClassApproval.class.getSimpleName();
private TabLayout tabLayout;
private ViewPager viewPager;
public LeaveClassApproval() {
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.hr_employee_leave_class, container, false);
tabLayout = view.findViewById(R.id.tabs);
viewPager = view.findViewById(R.id.view_pager);
viewPager.setAdapter(new LeaveAdapterApproval(getChildFragmentManager()));
tabLayout.setupWithViewPager(viewPager);
viewPager.setCurrentItem(1, true); // I set it here
return view;
}
}
But why LeaveClassApproval open LeaveFragmentToAll and LeaveFragmentToApproved, should not it be opening LeaveFragmentToPending instead. Is this a fault or is it a Fragment function?
I searched through Google but did not find an answer
So how to fix it?
Sounds like your problem is the default functionallity from the ViewPager itself. It has a variable called offscreenPageLimit, where the default value is 1. Means it'll always load one page to the left and one to the right. And you won't be able to set it to zero, because the source code of android behind it looks like this:,
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
If you realy want to get rid of this behaviour one thing you could do is building your own Pager class based on the android source code and simply try to cut out this functionallity.
By default the ViewPager opens the current Fragment and its two neighboring ones (that is, the one before and the one after in the data). If you want to change the default behavior use viewPager.setOffscreenPageLimit(int numberOfFragmentsToBePreloadedOnEachSideOfTheCurrentFragment). Notice, however, that the ViewPager will always load at least its two neighbors.
From the docs:
int getOffscreenPageLimit ()
Returns the number of pages that will be retained to either side of
the current page in the view hierarchy in an idle state. Defaults to
1.
I have a PagerAdapter which creates 3 fragments.
In the MainActivity I set the ViewPager like this:
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setOffscreenPageLimit(2);
pager.setAdapter(new PagerAdapter(getSupportFragmentManager()));
the pager.setOffScreenPageLimit(2) is from here https://stackoverflow.com/a/11852707/1662033, to make sure OnViewCreated is called once for each fragment.
Here is my PagerAdapter class:
public class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Home";
case 1:
return "Live";
case 2:
return "Gallery";
default:
return null;
}
}
#Override
public int getCount() {
return 3;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new HomeFragment();
case 1:
return new LiveFragment();
case 2:
return new GalleryFragment();
default:
return null;
}
}
}
In the current code: all of the fragments's onCreateView, onActivityCreated etc are called once, at the beginning and that's it.
The issue I am having is - in one of the fragments (LiveFragment) I have a custom view which connects to a camera and shows the live stream.
What I want is - to inflate the view of LiveFragment only when the user navigates to the fragment, instead of how its now - its inflated at the beginning with the other fragments.
Is there a way to call onCreateView only when fragment is chosen?
FragmentPagerAdapter creates all the Fragments and has all of them in memory at all time. i.e. All your Fragments are created only once and you can navigate around them.
FragmentStatePagerAdapter creates and has only 3 Fragments (the current Fragment and the left and right Fragments to the current one) in memory at any given time, by default. You cannot reduce that number. However, you can increase the number Fragments in memory by using the viewpager.setOffScreenPageLimit().
Since you have only 3 Fragments, all your 3 fragments are created when the Viewpager is initialised. You can track which Fragment is currently visible on the screen using viewpager.addOnPageChangeListener(). Using this you can change the View of your LiveFragment from dummy one to actual View only when the Fragment is currently visible.
How can I avoid a recyclerview to reload when the tabs change?
For example, I have a TabLayout with three Tabs, my ViewPager is:
class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new Tab1Fragment();
case 1:
return new Tab2Fragment();
case 2:
return new Tab3Fragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "tab1";
case 1:
return "tab2";
case 2:
return "tab3";
}
return null;
}
}
In the first one, I have a recyclerview, when I change the tabs and back to the first one, the list is reloaded.
There is a way do avoid this reload? Just keep the data loaded.
Android keeps only one fragment on either side of the current fragment in a ViewPager by default. So when you come back to your first tab from the third one, it will recreate the first tab which is what is happening in your case. Here is the documentation.
You need to keep all the Fragments in memory with
viewPager.setOffscreenPageLimit(<(number of fragments in view pager) - 1>) which is 2 in your case.
Of course, as the docs suggest, this number should be kept low especially if its a complex layout.
yourViewPager.setOffscreenPageLimit(2);
I have a View Pager with 5 pages which will show the 5 different data on 5 View Pages. I want that it should load the data for all the pages in one go.
Requirement: When user opens the View Pager screen for the first time, then it should load all the 5 pages i.e when user swipes for next page it should show the data of the other page without Loading.
I have implemented the same, with progress bar at every View Pager PAGE. i.e when user swipes, the progress dialog comes to fetch the data from server and show.
Let me know if anybody has best or optimized to achieve my requirement.
My Example Pager
public class BookMagListViewPagerAdapter extends FragmentPagerAdapter{
private FragmentManager fragmentManager ;
private int[] bookMagListOptionArray;
public BookMagListViewPagerAdapter(FragmentManager fm, int[] bookMagListArray) {
super(fm);
this.fragmentManager = fm;
this.bookMagListOptionArray = bookMagListArray;
// TODO Auto-generated constructor stub
}
#Override
public Fragment getItem(int arg0) {
Fragment fragment = null;
switch (arg0) {
case BookMagListFragment.OFFERS:
fragment = new BookMagOfferListFragment();
loadDATA(OFFERS);
break;
case BookMagListFragment.ALL_CAT:
fragment = new BookMagAllCatListFragment();
loadDATA(ALL_CAT);
break;
case BookMagListFragment.BEST_SELLER:
fragment = new BookMagBestSellerListFragment();
loadDATA(BEST_SELLER);
break;
case BookMagListFragment.REGIONAL:
fragment = new BookMagRegionalListFragment();
loadDATA(REGIONAL);
break;
case BookMagListFragment.FREE:
fragment = new BookMagFreeListFragment();
loadDATA(FREE);
break;
default:
break;
}
return fragment;
}
#Override
public int getCount() {
// TODO Auto-generated method stub
return this.bookMagListOptionArray.length;
}
}
You haven't a lot of page. So, you can create the views of all of your fragments when viewpager creating. And you should loading data inside of fragment's onCreate() or onCreateView().
mViewPager.setOffscreenPageLimit(5);
check this method at developer site
I've integrated PagerSlidingTabStrips in my application which runs as expected on Swipe of tabs.
But selecting tabs on PagerSlidingTabStrips doesn't switch to that fragment which works perfectly on swiping between tabs.
FragmentManager fm = getSupportFragmentManager();
mPager = (ViewPager) findViewById(R.id.pager);
ViewPagerAdapter viewpageradapter = new ViewPagerAdapter(fm);
mPager.setAdapter(viewpageradapter);
pagerSlidingTabStrp = (PagerSlidingTabStrip) findViewById(R.id.pager_sliding_tab_strip);
pagerSlidingTabStrp.setShouldExpand(true);
pagerSlidingTabStrp.setViewPager(mPager);
pagerSlidingTabStrp.setOnPageChangeListener(ViewPagerListener);
// Capture ViewPager page swipes
ViewPager.SimpleOnPageChangeListener ViewPagerListener = new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
switch(position)
{
case 0:
setMotherActionBarTitle(getString(R.string.str_all_contacts_fragment));
break;
case 1:
setMotherActionBarTitle(getString(R.string.str_group_contacts_fragment));
break;
case 2:
setMotherActionBarTitle(getString(R.string.str_call_logs_fragment));
break;
}
}
};
ViewPagerAdapter
public class ViewPagerAdapter extends FragmentPagerAdapter implements IconTabProvider {
final int PAGE_COUNT = 3;
private final int[] ICONS = { R.drawable.tab_icon_zname_contact_selector, R.drawable.tab_icon_zname_friends_selector,
R.drawable.tab_icon_zname_call_log_selector };
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int item) {
switch (item) {
case 0:
ContactsFragment homeFragment = new ContactsFragment();
return homeFragment;
case 1:
GroupsFragment groupsFragment = new GroupsFragment();
return groupsFragment;
case 2:
CallLogsFragment callLogsFragment = new CallLogsFragment();
return callLogsFragment;
}
return null;
}
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public int getPageIconResId(int position) {
return ICONS[position];
}
}
How to make selecting tabs works with PagerSlidingTabStrips? Morever PagerSlidingTabStrips tabs are not even clickabe.
Did I missed out to implement something? Or to add pagerSlidingTabStrips.setClickable(true)? or something?
Again the selector of PagerSlidingTabStrips tabs not selected proper drawable with selector. What could be possible the reason for that?
Here's a picture of it.
I've found out why clicking tab was not working with PagerSlidingTabStrips after I've looked out for other options with sliding tabs functionality like SlidingTabsLayout which also was not able to click tabs.
Found out problem was with my xml layout which covers whole as ViewPager which doesn't make tabs clickable of eitherPagerSlidingTabStrips or SlidingTabsLayout works.
<com.netdoers.zname.utils.PagerSlidingTabStrip
android:id="#+id/pager_sliding_tab_strip"
android:layout_width="fill_parent"
android:layout_height="45dip"
android:background="#android:color/white"
app:pstsDividerColor="#FFFFA500"
app:pstsIndicatorColor="#FFFFA500"
app:pstsTabPaddingLeftRight="14dip"
app:pstsUnderlineColor="#FFFFA500" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="45dp" />
Leaving same height in Viewpager as of PagerSlidingTabStips from top where PagerSlidingTabStrips rendered solved my issue.
Indeed the issue is in XML. The issue probably due to RelativeLayout is used om the XML. Consider using LinearLayout will ensure there's no overlap between views.