We will try to accomplish the following scenario:
MainActivity | | ContainerFragment | | |_ _ _ Tab A | |_ _ _ Fragment 1 | | | |_ _ _ Fragment 2 | | | |_ _ _ Fragment 3 | | | |_ _ _ ... | |_ _ _ Tab B | |_ _ _ Fragment 4 | | | |_ _ _ Fragment 5 | | | |_ _ _ Fragment 6 | | | |_ _ _ ... | |_ _ _ Tab C | |_ _ _ Fragment 7 | | | |_ _ _ Fragment 8 | | | |_ _ _ Fragment 8 | | | |_ _ _ ...
Check out the apk file and/or video to see the final result before going further:
https://raw.githubusercontent.com/tausiq/ViewPagerMultipleFragmentDemo/master/release/app_0.0.1.apk
There is one MainActivity and inside the MainActivity there is one Fragment, which is a container of a ViewPager with 3 tabs.
We would like to maintain multiple Fragment stack or multiple child fragments inside each tab.
This can be easily accomplished with the help of ChildFragmentManager.
Lets start with the MainActivity class. We have only one Activity class for simplicity and demo purpose.
MainActivity.java
import android.os.Bundle; import android.support.v4.app.FragmentManager; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; public class MainActivity extends ActionBarActivity { private CarouselFragment carouselFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); actionBar.setHomeButtonEnabled(true); if (savedInstanceState == null) { // withholding the previously created fragment from being created again // On orientation change, it will prevent fragment recreation // its necessary to reserve the fragment stack inside each tab initScreen(); } else { // restoring the previously created fragment // and getting the reference carouselFragment = (CarouselFragment) getSupportFragmentManager().getFragments().get(0); } } private void initScreen() { // Creating the ViewPager container fragment once carouselFragment = new CarouselFragment(); final FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.container, carouselFragment) .commit(); } /** * Only Activity has this special callback method * Fragment doesn't have any onBackPressed callback * * Logic: * Each time when the back button is pressed, this Activity will propagate the call to the * container Fragment and that Fragment will propagate the call to its each tab Fragment * those Fragments will propagate this method call to their child Fragments and * eventually all the propagated calls will get back to this initial method * * If the container Fragment or any of its Tab Fragments and/or Tab child Fragments couldn't * handle the onBackPressed propagated call then this Activity will handle the callback itself */ @Override public void onBackPressed() { if (!carouselFragment.onBackPressed()) { // container Fragment or its associates couldn't handle the back pressed task // delegating the task to super class super.onBackPressed(); } else { // carousel handled the back pressed task // do not call super } } }
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent" />
CarouselFragment.java
import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.view.ViewPager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.viewpagerindicator.TabPageIndicator; /** * A simple {@link Fragment} subclass. * */ public class CarouselFragment extends Fragment { /** * TabPagerIndicator * * Please refer to ViewPagerIndicator library */ protected TabPageIndicator indicator; protected ViewPager pager; private ViewPagerAdapter adapter; public CarouselFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View rootView = inflater.inflate(R.layout.fragment_carousel, container, false); indicator = (TabPageIndicator) rootView.findViewById(R.id.tpi_header); pager = (ViewPager) rootView.findViewById(R.id.vp_pages); return rootView; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Note that we are passing childFragmentManager, not FragmentManager adapter = new ViewPagerAdapter(getResources(), getChildFragmentManager()); pager.setAdapter(adapter); indicator.setViewPager(pager); } /** * Retrieve the currently visible Tab Fragment and propagate the onBackPressed callback * * @return true = if this fragment and/or one of its associates Fragment can handle the backPress */ public boolean onBackPressed() { // currently visible tab Fragment OnBackPressListener currentFragment = (OnBackPressListener) adapter.getRegisteredFragment(pager.getCurrentItem()); if (currentFragment != null) { // lets see if the currentFragment or any of its childFragment can handle onBackPressed return currentFragment.onBackPressed(); } // this Fragment couldn't handle the onBackPressed call return false; } }
fragment_carousel.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <com.viewpagerindicator.TabPageIndicator android:id="@+id/tpi_header" android:layout_width="match_parent" android:layout_height="wrap_content"/> <android.support.v4.view.ViewPager android:id="@+id/vp_pages" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" /> </LinearLayout>
ViewPagerAdapter.java
import android.content.res.Resources; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.util.SparseArray; import android.view.ViewGroup; import ms.cloudtea.viewpagermultiplefragmentdemo.app.frags.A1Fragment; import ms.cloudtea.viewpagermultiplefragmentdemo.app.frags.B1Fragment; import ms.cloudtea.viewpagermultiplefragmentdemo.app.frags.C1Fragment; /** * Created by shahabuddin on 6/6/14. */ public class ViewPagerAdapter extends FragmentPagerAdapter { private final Resources resources; SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>(); /** * Create pager adapter * * @param resources * @param fm */ public ViewPagerAdapter(final Resources resources, FragmentManager fm) { super(fm); this.resources = resources; } @Override public Fragment getItem(int position) { final Fragment result; switch (position) { case 0: // First Fragment of First Tab result = new A1Fragment(); break; case 1: // First Fragment of Second Tab result = new B1Fragment(); break; case 2: // First Fragment of Third Tab result = new C1Fragment(); break; default: result = null; break; } return result; } @Override public int getCount() { return 3; } @Override public CharSequence getPageTitle(final int position) { switch (position) { case 0: return resources.getString(R.string.page_1); case 1: return resources.getString(R.string.page_2); case 2: return resources.getString(R.string.page_3); default: return null; } } /** * On each Fragment instantiation we are saving the reference of that Fragment in a Map * It will help us to retrieve the Fragment by position * * @param container * @param position * @return */ @Override public Object instantiateItem(ViewGroup container, int position) { Fragment fragment = (Fragment) super.instantiateItem(container, position); registeredFragments.put(position, fragment); return fragment; } /** * Remove the saved reference from our Map on the Fragment destroy * * @param container * @param position * @param object */ @Override public void destroyItem(ViewGroup container, int position, Object object) { registeredFragments.remove(position); super.destroyItem(container, position, object); } /** * Get the Fragment by position * * @param position tab position of the fragment * @return */ public Fragment getRegisteredFragment(int position) { return registeredFragments.get(position); } }
Each Tab Fragment and the child Fragments of each Tab Fragment need to implement an Interface OnBackPressListener
OnBackPressListener.java
public interface OnBackPressListener { public boolean onBackPressed(); }
Common implementation of OnBackPressListener interface. This implementation should be used by all Tab Fragments and their child Fragments.
BackPressImpl.java
import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; /** * Created by shahabuddin on 6/6/14. */ public class BackPressImpl implements OnBackPressListener { private Fragment parentFragment; public BackPressImpl(Fragment parentFragment) { this.parentFragment = parentFragment; } @Override public boolean onBackPressed() { if (parentFragment == null) return false; int childCount = parentFragment.getChildFragmentManager().getBackStackEntryCount(); if (childCount == 0) { // it has no child Fragment // can not handle the onBackPressed task by itself return false; } else { // get the child Fragment FragmentManager childFragmentManager = parentFragment.getChildFragmentManager(); OnBackPressListener childFragment = (OnBackPressListener) childFragmentManager.getFragments().get(0); // propagate onBackPressed method call to the child Fragment if (!childFragment.onBackPressed()) { // child Fragment was unable to handle the task // It could happen when the child Fragment is last last leaf of a chain // removing the child Fragment from stack childFragmentManager.popBackStackImmediate(); } // either this Fragment or its child handled the task // either way we are successful and done here return true; } } }
All the Tab Fragments and child Fragments of each Tab should be a sub class of a common Fragment class. This super class will handle the common onBackPressed callback
RootFragment.java
import android.support.v4.app.Fragment; import ms.cloudtea.viewpagermultiplefragmentdemo.app.BackPressImpl; import ms.cloudtea.viewpagermultiplefragmentdemo.app.OnBackPressListener; /** * Created by shahabuddin on 6/6/14. */ public class RootFragment extends Fragment implements OnBackPressListener { @Override public boolean onBackPressed() { return new BackPressImpl(this).onBackPressed(); } }
You can get the full source code from here:
I see in you code a lot of empty constructors in Fragments with comment “Required empty public constructor”. I belive they all useless becouse Java automatically create empty public constructor if there no any other constructors. This is requared only if there some constructor with params.
where is project file
why don’t execute when i copy folder(maybe project folder) into my eclipse workspace.
ClasscastException at:
OnBackPressListener currentFragment = (OnBackPressListener) mSectionsPagerAdapter.getRegisteredFragment(mViewPager.getCurrentItem());
@Mursaleen,
All the associated Fragments need to implement “OnBackPressListener” interface.
Notice that new fragments are not replacing previous fragments.
It seems like they are being replaced because of android:background=”@android:color/white” in added fragments.
Is there a way to determine when app is about to throw a StackOverFlow for creating too many Fragments (and possible do something about it) ?
Hi.
I’m trying to implement your source code but I’m getting errors with the call to getFragments() and getChildFragmentManager() methods. They seem to not be recognized as part of Fragment() method, and I suppose is due to some kind of conflict between libraries v4 and v7.
So the question is: how should I add android-support-v4 and android-support-v7?
Thanks.
@Walter Gallego Gómez
If you are using Android Studio latest version, then the support library should get added by default. Otherwise, you can follow this post,
http://stackoverflow.com/questions/16746558/android-gradle-build-and-the-support-library/17375109#17375109
If by any chance you are not using Android Studio, then using of Android Studio is recommended.
I am unable to import and run downloaded project file.Please share any body have updated code for same.
This post really helped me a lot. Thank you. I have successfully implemented this project for my personal project. Hope to see this type of good posts in future 🙂
It worked fine for me.. Thanks a lot. Was exactly looking for this.
Reblogged this on Tech Code Geek.
This link helped me solve the fragment replacement issue. http://stackoverflow.com/questions/17544729/fragment-replace-not-working-when-using-childfragmentmanager-in-android
As Gero mentioned… new fragments appear in front of parent fragments, but parent fragments are active as well as their listeners.
I tried to hide parent fragment with beginTransaction().hide().commit(); but this also hides the new child fragment. The only hack I could come up with was to set a big click listener for my new child fragment’ layout.
In the tutorial, it’s only for if you have one activity with one fragment container. I have managed to implement this successfully. But how do you go about it if you have one activity with multiple fragment container that hold view pager. In other words, in your app you have more than one tabbed layout.
A2Fragment button click is not working in marshmallow.
Please Help me..
This is awesome, thank you very much!
@Gero comment is the same problem i am having. Fraagmnets are not getting replaced but they are overlapping. Does anyone know what is the problem here?
hi…how can i go directly from fragment A1 to B2, for example?
Hi everyone! Who can tell me why FrameLayout with @id fragment_mainLayout is container??? tks you ❤
This was really helpful and this is the exact thing i was searching for days.i have one question on how to show the tabs in the bottom of the screen?
Could anyone tell please tell me how to manually apply backpress using above code?
I am calling onBackPressed() method on a button click but nothing is happening.
Thanks in advance!
Can anyone help me, regarding the fragment overlapping issue, using this example.
Due to Fragment overlapping, the controls from back fragment are accessible in the top fragment.
set clickable=”true” on every parent layout of xml of fragment to overcome from this issue.
Hi, is your implementation supposed to restore the state, when home button clicked and application reopened like in the rotating. And you are using the if (savedInstanceState == null) check on oncreate just for rotation. I mean if I am disable rotation in my app, can I change the code to;
if (savedInstanceState == null) {
initScreen();
} else {
carouselFragment = (CarouselFragment) getSupportFragmentManager().getFragments().get(0);
}
to just
initScreen();
Will there be any side affects and edge cases that I cannot predict now?
Gud 1 dude
Is there anyway for confirming before leaving each fragment? Handling Back key in fragment is causing a big mess.
hello,what is TabPageIndicator? i am only getting error here in CarouselFragment.java
help me with this?
Dude.. Thanks a lot man.. You did an amazing work.. Can’t thank you enough
I had the problem with parentFragment.getFragmentManager().getBackStackEntryCount()
it returned 0 always. I add to back stack my fragment like this: transaction.addToBackStack(getString(this.getId()));
it was null in example
Thanks for this sample, I have implemented it successfully but the problem is that child fragment does not replace parent fragment.