How to add a Fragment inside a ViewPager using Nested Fragment (Android 4.2)
S

4

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.

Soothsay answered 14/11, 2012 at 12:52 Comment(1)
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.Butterfish
D
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

Dicks answered 15/11, 2012 at 3:59 Comment(14)
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 :(Headley
Hi Straya. I have tested my example with SherlockFragments and I didnt see an error. What is the error you are seeing?Dicks
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.Headley
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.Dicks
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!Headley
@Headley You need to replace the support lib inside the ABS project with the latest (v11) and it should work.Insentient
@Insentient I'd already done that, of course ;) My fix was as I described above.Headley
@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.Planksheer
I think the error might be due an error in the fragment itself and not because of the nesting.Dicks
@Marco it gives me Error Inflating Class. if you want more illustration I can raise a question for that and mention you in it.Planksheer
@Marco Here it's #23180589Planksheer
@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.Harvest
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 pagerHarvest
@ChanjungKim I've updated the sample. The full code can be found in the repo link above.Dicks
H
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.

Herthahertz answered 14/11, 2012 at 15:27 Comment(10)
Thank you so much, I'm trying and refactoring to use with XML layout files and seems to work fine ;)Soothsay
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?Soothsay
@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.Butterfish
@Soothsay 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.Herthahertz
@Butterfish 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.Herthahertz
@Soothsay See my edited answer and see if the edit answers some of your questions.Herthahertz
@Luksprog: I have issue to get Previous Fragment from child Fragment on android back press key.Heffner
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.Heffner
@Butterfish 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.Heffner
@Heffner Please post a new question adding related code and clearly explaining what is the problem.Herthahertz
R
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

Renz answered 22/9, 2014 at 18:30 Comment(2)
There are some bugs with this code such as it crashing on changing orientation. Did you ever fix them?Ordonez
Has anyone managed to fix the bug with this code that causes the app to crash on an orientation change?Ordonez
I
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.
Ileum answered 26/4, 2021 at 15:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.