56

I have a ViewPager with three Fragments, each one shows a List (or Grid).

In the new Android API level 17 (Jelly Bean 4.2), one of the features is Nested Fragments. The new functionality description says:

if you use ViewPager to create fragments that swipe left and right and consume a majority of the screen space, you can now insert fragments into each fragment page.

So, if I understand right, now I can create a ViewPager with Fragments (with a button inside for example) inside, and when user press the button show another Fragment without loose the ViewPager using this new feature.

I've expended my morning trying to implement this several different ways, but I can´t get it working... Can somebody add a simple example of how to implement this?

PS: I'm only interested in doing at this way, with getChildFragmentManager to learn how works.

CyberEd
  • 875
  • 9
  • 19
sabadow
  • 5,095
  • 3
  • 34
  • 51
  • 3
    I'll be working on a sample for this early next week. If nobody has chimed in with an answer by then, I'll try to post something at that time. – CommonsWare Nov 14 '12 at 13:37

4 Answers4

101

Assuming you have created the correct xml layouts. It is now very simple to display fragments in a ViewPager that is hosted by another Fragment.

The following is a parent fragment that displays child fragments:

class ParentViewPagerFragment : Fragment() {

  override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val root = inflater.inflate(R.layout.fragment_parent_viewpager, container, false)

    val viewPager = root.findViewById(R.id.viewPager) as ViewPager
    // Important: Must use the child FragmentManager or you will see side effects.
    viewPager.adapter = MyAdapter(childFragmentManager)

    val tabStrip = root.findViewById<TabLayout>(R.id.pagerTabStrip)
    tabStrip.setupWithViewPager(viewPager)

    return root
  }

  class MyAdapter internal constructor(fm: FragmentManager) : FragmentPagerAdapter(fm) {

    override fun getCount(): Int = 4

    override fun getItem(position: Int): Fragment {
      val args = Bundle().apply { putInt(ChildFragment.POSITION_KEY, position) }
      return ChildFragment.newInstance(args)
    }

    override fun getPageTitle(position: Int): CharSequence = "Tab $position"
  }

  companion object {
    val TAG: String = ParentViewPagerFragment::class.java.name
  }
}

It is important to use Fragment.getChildFragmentManager() when instantiating the FragmentPagerAdapter. Also note that you cannot use Fragment.setRetainInstance() on the children fragments or you'll get an exception. The imports were omitted for brevity.

Source code can be found at: https://github.com/marcoRS/nested-fragments

Marco RS
  • 8,145
  • 3
  • 37
  • 45
  • 1
    I'm seeing problems doing this (Fragment containing ViewPager, that is) with SherlockFragments and support.v4 release11, the ViewPager content gets set via adapter however the child fragments do not display :( – straya Nov 16 '12 at 04:58
  • Hi Straya. I have tested my example with SherlockFragments and I didnt see an error. What is the error you are seeing? – Marco RS Nov 16 '12 at 05:20
  • 1
    Heya, I've also got Android-ViewPagerIndicator in the mix. ViewPager and ViewPagerIndicator are displayed (I have a TextView above ViewPager in its Fragment so I can see that that Fragment is displayed), but the ViewPager's child Fragments (provided via a FragmentPagerAdapter) are not displayed. I can swipe between pages, Indicator updates but still not child fragments. – straya Nov 16 '12 at 05:35
  • Hi Straya, that could be the problem but I'm unsure. I have updated my github sample code to include a PagerTabStrip which is similar to the ViewPagerIndicator and it seems to work just fine. – Marco RS Nov 16 '12 at 06:16
  • 2
    Found that my FragmentPagerAdapter implemented its own isViewObject method, which wasn't a problem before nesting the ViewPager in its own fragment, once removed I now see the pages :) Pulled your code and ran it to boost my confidence and desire to get this working - so thanks Marco! – straya Nov 16 '12 at 07:10
  • @straya You need to replace the support lib inside the ABS project with the latest (v11) and it should work. – Adam Nov 20 '12 at 22:12
  • @Adam I'd already done that, of course ;) My fix was as I described above. – straya Nov 21 '12 at 02:11
  • @Marco I can't achieve this way due to it raises an Exception when meeting the first item while reading the XML file of the first Fragment, pare you have an idea about that? thanks in advnce. – Muhammed Refaat Apr 17 '14 at 13:53
  • I think the error might be due an error in the fragment itself and not because of the nesting. – Marco RS Apr 18 '14 at 00:30
  • @Marco it gives me `Error Inflating Class`. if you want more illustration I can raise a question for that and mention you in it. – Muhammed Refaat Apr 20 '14 at 07:17
  • @Marco Here it's http://stackoverflow.com/questions/23180089/binary-xml-file-line-15-error-inflating-class-unknown – Muhammed Refaat Apr 20 '14 at 09:08
  • @Marco, i am relatively new to android and specifically in fragments, i cannot implement the custom xml layouts at different adapter position, can you please guide how to merge different layouts with child fragments. – Umair Sep 14 '16 at 20:17
  • With these line of codes 'ViewPager mViewPager = (ViewPager) view.findViewById(R.id.viewPager); mViewPager.setAdapter(new MyAdapter(getChildFragmentManager()));' how could i bring different layouts inside view pager – Umair Sep 14 '16 at 20:18
  • 1
    @ChanjungKim I've updated the sample. The full code can be found in the repo link above. – Marco RS Feb 04 '19 at 00:10
12

Edit: If you want to replace all the content of a page in a ViewPager you could still use nested fragments, but some changes are needed. Check the sample below(the FragmentActivity, setting the ViewPager and the PagerAdapter are the same as the previous snippet of code):

// this will act as a fragment container, representing one page in the ViewPager
public static class WrapperFragment extends Fragment implements
        ReplaceListener {

    public static WrapperFragment newInstance(int position) {
        WrapperFragment wp = new WrapperFragment();
        Bundle args = new Bundle();
        args.putInt("position", position);
        wp.setArguments(args);
        return wp;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        FrameLayout fl = new FrameLayout(getActivity());
        fl.setId(10000);
        if (getChildFragmentManager().findFragmentByTag("initialTag") == null) {
            InitialInnerFragment iif = new InitialInnerFragment();
            Bundle args = new Bundle();
            args.putInt("position", getArguments().getInt("position"));
            iif.setArguments(args);
            getChildFragmentManager().beginTransaction()
                    .add(10000, iif, "initialTag").commit();
        }
        return fl;
    }

    // required because it seems the getChildFragmentManager only "sees"
    // containers in the View of the parent Fragment.   
    @Override
    public void onReplace(Bundle args) {
        if (getChildFragmentManager().findFragmentByTag("afterTag") == null) {
            InnerFragment iif = new InnerFragment();
            iif.setArguments(args);
            getChildFragmentManager().beginTransaction()
                    .replace(10000, iif, "afterTag").addToBackStack(null)
                    .commit();
        }
    }

}

// the fragment that would initially be in the wrapper fragment
public static class InitialInnerFragment extends Fragment {

    private ReplaceListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        mListener = (ReplaceListener) this.getParentFragment();
        LinearLayout ll = new LinearLayout(getActivity());
        Button b = new Button(getActivity());
        b.setGravity(Gravity.CENTER_HORIZONTAL);
        b.setText("Frame " + getArguments().getInt("position"));
        b.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Bundle args = new Bundle();
                args.putInt("positionInner",
                        getArguments().getInt("position"));
                if (mListener != null) {
                    mListener.onReplace(args);
                }
            }
        });
        ll.setOrientation(LinearLayout.VERTICAL);
        ll.addView(b, new LinearLayout.LayoutParams(250,
                LinearLayout.LayoutParams.WRAP_CONTENT));
        return ll;
    }

}

public static class InnerFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        TextView tv = new TextView(getActivity());
        tv.setText("InnerFragment in the outher Fragment with position "
                + getArguments().getInt("positionInner"));
        return tv;
    }

}

public interface ReplaceListener {
    void onReplace(Bundle args);
}

At a quick look it works, but issues may appear as I haven't tested it to much.

Can somebody show a simple example of how to do this?

Using nested fragments seems pretty easy, until Commonsware comes with a more elaborated sample you can try the code below:

public class NestedFragments extends FragmentActivity {

    @Override
    protected void onCreate(Bundle arg0) {
        super.onCreate(arg0);
        ViewPager vp = new ViewPager(this);
        vp.setId(5000);
        vp.setAdapter(new MyAdapter(getSupportFragmentManager()));
        setContentView(vp);
    }

    private static class MyAdapter extends FragmentPagerAdapter {

        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int position) {
            return WrapperFragment.newInstance(position);
        }

        @Override
        public int getCount() {
            return 8;
        }
    }

    public static class WrapperFragment extends Fragment {

        public static WrapperFragment newInstance(int position) {
            WrapperFragment wp = new WrapperFragment();
            Bundle args = new Bundle();
            args.putInt("position", position);
            wp.setArguments(args);
            return wp;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            LinearLayout ll = new LinearLayout(getActivity());
            FrameLayout innerFragContainer = new FrameLayout(getActivity());
            innerFragContainer.setId(1111);
            Button b = new Button(getActivity());
            b.setText("Frame " + getArguments().getInt("position"));
            b.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    InnerFragment innerFragment = new InnerFragment();
                    Bundle args = new Bundle();
                    args.putInt("positionInner",
                            getArguments().getInt("position"));
                    innerFragment.setArguments(args);
                    FragmentTransaction transaction = getChildFragmentManager()
                            .beginTransaction();
                    transaction.add(1111, innerFragment).commit();
                }
            });
            ll.setOrientation(LinearLayout.VERTICAL);
            ll.addView(b, new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            ll.addView(innerFragContainer, new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.MATCH_PARENT,
                    LinearLayout.LayoutParams.MATCH_PARENT));
            return ll;
        }

    }

    public static class InnerFragment extends Fragment {

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            TextView tv = new TextView(getActivity());
            tv.setText("InnerFragment in the outher Fragment with position "
                    + getArguments().getInt("positionInner"));
            return tv;
        }

    }

}

I was lazy and made everything in code but I'm sure it can work with inflated xml layouts.

user
  • 86,916
  • 18
  • 197
  • 190
  • 1
    Thank you so much, I'm trying and refactoring to use with XML layout files and seems to work fine ;) – sabadow Nov 14 '12 at 15:56
  • I think that where you use `transaction.add(1111, innerFragment).commit();` should use the `replace` method, because every times the button is pressed add to the hierarchy a layout. Also I refactor the example to use XML Layouts and try to change to replace **all** the content of the page. But I couldn't made it yet, always the `Button` keep on top. Any suggestion? – sabadow Nov 14 '12 at 16:38
  • @Luksprog: This isn't the scenario I was thinking of. I'm going to be working on a sample where the `ViewPager` itself is hosted by a fragment and has fragments for its pages. That *should* just be a matter of passing `getChildFragmentManager()` to the `PagerAdapter`, but I want to try it first. Your example is demonstrating a `ViewPager` holding fragments that themselves hold other fragments -- perfectly reasonable, just not what I was thinking when I read the question. – CommonsWare Nov 14 '12 at 16:54
  • @sabadow I used `add` as an example, you could use `replace` it it fits your needs. The Button remains there in my sample because the `replace`(or `add`) method doesn't remove the existing views from the container(set the height of `innerFragContainer` to `WRAP_CONTENT` and you'll see that the existing views remain with the addition of the new fragment at the bottom). And it will be difficult to make what you're trying to do because of the way the `ViewPager` works. I'll into a solution tomorrow. – user Nov 14 '12 at 21:18
  • 2
    @CommonsWare I actually hope the use wants that. Anyway, I've tested passing `getChildFragmentManager` to a `PagerAdapter` set for a `ViewPager` contained in a `Fragment` and it works quite well. – user Nov 14 '12 at 21:21
  • @sabadow See my edited answer and see if the edit answers some of your questions. – user Nov 15 '12 at 10:59
  • @Luksprog: I have issue to get Previous Fragment from child Fragment on android back press key. – Herry Mar 07 '13 at 07:26
  • let me explain what i have done. I have use Support Api demo ViewPager to make tabs with swipe. Now on Second Tabs contain one FrameLayout which Load real Fragment which have list of items and now when i tap on item it will replace that FrameLayout with new detail Item Fragment.All this things work fine only issue is that when i am at detail page and press back button it destroy activity instead of show previous fragment which was list of items. – Herry Mar 07 '13 at 07:30
  • @CommonsWare i have try your code which show One Button on top and when tap on it show Button and Textview below it.Now when i press android back button it destroy activity but i want to show previous fragment state which will be only Button no Textview below it. – Herry Mar 07 '13 at 07:45
  • @Herry Please post a new question adding related code and clearly explaining what is the problem. – user Mar 07 '13 at 08:12
6

I have created a ViewPager with 3 elements and 2 sub elements for index 2 and 3 and here what I wanted to do..

enter image description here

I have implemented this with the help from previous questions and answers from StackOverFlow and here is the link.

ViewPagerChildFragments

Rukmal Dias
  • 3,242
  • 1
  • 34
  • 28
0

Easily use Navigation Component in your app! then in your listener, copy this code:

findNavController().navigate('Your action Id)
  • Your action id which IDE created automatically for you.
Parsamlm
  • 52
  • 7