Как добавить фрагмент внутри ViewPager с помощью Nested Fragment (Android 4.2)

У меня есть ViewPager с тремя Fragments, каждый из которых показывает List (или Grid).

В новом Android API level 17 (Jelly Bean 4.2) одна из функций Вложенные фрагменты. Описание новой функциональности говорит:

если вы используете ViewPager для создания фрагментов, которые прокручиваются влево и вправо и потребляют большую часть пространства экрана, теперь вы можете вставлять фрагменты на каждую страницу фрагмента.

Итак, если я правильно понимаю, теперь я могу создать ViewPager с Fragments (например, внутри внутри внутри), и когда пользователь нажимает кнопку, показывается еще один Fragment, не теряя ViewPager, используя эта новая функция.

Я потратил свое утро, пытаясь реализовать это несколько разных способов, но я не могу заставить его работать... Может ли кто-нибудь добавить простой пример того, как это реализовать?

PS: Меня это интересует только с помощью getChildFragmentManager, чтобы узнать, как это работает.

Ответ 1

Предполагая, что вы создали правильные макеты XML. Теперь очень просто отобразить фрагменты в ViewPager, который размещен в другом фрагменте.

Ниже представлен родительский фрагмент, который отображает дочерние фрагменты:

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
  }
}

При создании экземпляра FragmentPagerAdapter важно использовать Fragment.getChildFragmentManager(). Также обратите внимание, что вы не можете использовать Fragment.setRetainInstance() для дочерних фрагментов, иначе вы получите исключение. Импорт был опущен для краткости.

Исходный код можно найти по адресу: https://github.com/marcoRS/nested-fragments

Ответ 2

Edit: Если вы хотите заменить все содержимое страницы в ViewPager, вы все равно можете использовать вложенные фрагменты, но необходимы некоторые изменения. Проверьте образец ниже (FragmentActivity, установка ViewPager и PagerAdapter совпадают с предыдущим фрагментом кода):

// 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);
}

При быстром просмотре он работает, но проблемы могут возникать, поскольку я не тестировал его много.

Может ли кто-нибудь показать простой пример того, как это сделать?

Использование вложенных фрагментов кажется довольно простым, пока Commonsware не предложит более продуманный образец, который вы можете попробовать следующим образом:

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;
        }

    }

}

Я ленился и сделал все в коде, но я уверен, что он может работать с раздутыми макетами xml.

Ответ 3

Я создал ViewPager с 3 элементами и 2 подэлементами для индекса 2 и 3, и вот что я хотел сделать.

enter image description here

Я реализовал это с помощью предыдущих вопросов и ответов от StackOverFlow и вот ссылка.

ViewPagerChildFragments