Фрагмент внутри фрагмента

141

Мне нужна помощь в работе с фрагментом внутри фрагмента, на самом деле я столкнулся с проблемой при нажатии кнопки возврата. На главном экране приложения есть кнопки, и нажатие на каждую кнопку заменяет новый фрагмент (и этот фрагмент содержится внутри другого фрагмента), динамическое добавление / замена фрагмента работает нормально, при нажатии кнопки button1 фрагмент заменен, то же самое происходит при нажатии кнопки, но если я нажимаю кнопка снова, получилось исключение:

"Duplicate id 0x7f05000a, tag null, or parent id 0x7f050009 with
another fragment for com........ fragmentname"

означает, что фрагмент или внутренние фрагменты уже добавлены, и я пытаюсь добавить их снова, кто-нибудь знает, как работать с фрагментом внутри фрагмента и перемещаться вперед и назад без каких-либо проблем, спасибо за поддержку.

MainActivity, где фрагменты динамически добавляются и заменяются.

public class FragmentInsideFragmentTestActivity extends Activity {

    private Button button1;
    private Button button2;
    private Button button3;
    private Button button4;


    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        button1 =(Button) this.findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
           public void onClick(View view) {
               onButtonClick(view);
            }
        });

        button2 =(Button) this.findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                onButtonClick(view);
            }
        });

        button3 =(Button) this.findViewById(R.id.button3);
        button3.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
               onButtonClick(view);
            }
        });

        button4 =(Button) this.findViewById(R.id.button4);
        button4.setOnClickListener(new View.OnClickListener() {
           public void onClick(View view) {
               onButtonClick(view);
           }
        });
    }

    public void onButtonClick(View v) {
        Fragment fg;

        switch (v.getId()) {
           case R.id.button1:
                   fg=FirstFragment.newInstance();
                   replaceFragment(fg);
                   break;
           case R.id.button2:
                   fg=SecondFragment.newInstance();
                   replaceFragment(fg);
                   break;
           case R.id.button3:
                   fg=FirstFragment.newInstance();
                   replaceFragment(fg);
                   break;
           case R.id.button4:
                   fg=SecondFragment.newInstance();
                   replaceFragment(fg);
                   break;
        }
    }

    private void replaceFragment(Fragment newFragment) {
       FragmentTransaction trasection = getFragmentManager().beginTransaction();

        if(!newFragment.isAdded()) {
            try {
                //FragmentTransaction trasection =
                getFragmentManager().beginTransaction();
                trasection.replace(R.id.linearLayout2, newFragment);
                trasection.addToBackStack(null);
                trasection.commit();
            } catch (Exception e) {
                // TODO: handle exception
                // AppConstants.printLog(e.getMessage());
            } else {
                trasection.show(newFragment);
            }
        }
    }

Вот макет: main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/linearLayout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button1" />

        <Button
            android:id="@+id/button2"
            android:text="Button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/button3"
            android:text="Button3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/button4"
            android:text="Button4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" />
</LinearLayout>

Надеюсь, я попытался решить свою проблему.

Иджаз Ахмед
источник
1
Приведенный выше код отлично работает у меня с Android 3.1
Вивек
3
возможный дубликат фрагментов внутри фрагментов
Владимир
для получения дополнительной информации см. stackoverflow.com/a/11020531/1219456
Раниз Ахмед

Ответы:

287

AFAIK, фрагменты не могут содержать другие фрагменты.


ОБНОВИТЬ

С текущими версиями пакета поддержки Android - или собственными фрагментами на уровне API 17 и выше - вы можете вкладывать фрагменты с помощью getChildFragmentManager(). Обратите внимание, что это означает, что вам необходимо использовать версию фрагментов из пакета поддержки Android на уровнях API 11–16, потому что, хотя на этих устройствах есть собственная версия фрагментов, в этой версии нет getChildFragmentManager().

CommonsWare
источник
31
Платформа здесь действительно отстой. Я потратил три часа на отладку, потому что внутренний фрагмент будет нормально отображаться в первый раз, но исчезнет после изменения ориентации экрана. Никаких исключений, информация журнала, ничего. Исправлено переключение на внутренний фрагмент getChildFragmentManager()и удаление setRetainInstance(true)из него (жалко). Спасибо, что снова спас мой бекон, @CommonsWare.
Феликс
87

введите описание изображения здесь

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

Деятельность

activity_main.xml

Добавьте FrameLayoutк своей активности родительский фрагмент.

<?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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Activity"/>

    <FrameLayout
        android:id="@+id/parent_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="200dp"/>

 </LinearLayout>

MainActivity.java

Загрузите родительский фрагмент и реализуйте прослушиватели фрагментов. (См. Сообщение фрагмента .)

import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity implements ParentFragment.OnFragmentInteractionListener, ChildFragment.OnFragmentInteractionListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Begin the transaction
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.parent_fragment_container, new ParentFragment());
        ft.commit();
    }

    @Override
    public void messageFromParentFragment(Uri uri) {
        Log.i("TAG", "received communication from parent fragment");
    }

    @Override
    public void messageFromChildFragment(Uri uri) {
        Log.i("TAG", "received communication from child fragment");
    }
}

Родительский фрагмент

fragment_parent.xml

Добавьте еще один FrameLayoutконтейнер для дочернего фрагмента.

<?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"
              android:layout_margin="20dp"
              android:background="#91d0c2">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Parent fragment"/>

    <FrameLayout
        android:id="@+id/child_fragment_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </FrameLayout>

</LinearLayout>

ParentFragment.java

Используйте getChildFragmentManagerin onViewCreatedдля настройки дочернего фрагмента.

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;    

public class ParentFragment extends Fragment {

    private OnFragmentInteractionListener mListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_parent, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        Fragment childFragment = new ChildFragment();
        FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
        transaction.replace(R.id.child_fragment_container, childFragment).commit();
    }


    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void messageFromParentFragment(Uri uri);
    }
}

Дочерний фрагмент

fragment_child.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"
              android:layout_margin="20dp"
              android:background="#f1ff91">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Child fragment"/>
</LinearLayout>

ChildFragment.java

Здесь тоже нет ничего особенного.

import android.support.v4.app.Fragment;

public class ChildFragment extends Fragment {

    private OnFragmentInteractionListener mListener;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_child, container, false);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }


    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void messageFromChildFragment(Uri uri);
    }
}

Примечания

Suragch
источник
Когда я пытаюсь перейти к другому фрагменту из ParentFragment, все в порядке, затем я возвращаюсь к ParentFragment и получаю исключение: java.lang.IllegalStateException: у указанного дочернего элемента уже есть родитель. Вы должны сначала вызвать removeView () для родительского элемента ребенка.
Рондев,
70

Начиная с Android 4.2 (API 17) становятся доступны вложенные фрагменты http://developer.android.com/about/versions/android-4.2.html#NestedFragments

Чтобы разместить фрагмент внутри другого фрагмента, используйте getChildFragmentManager ()

Он также доступен в библиотеке поддержки!

Ник
источник
Обновите свой ответ, чтобы упомянуть, что поддержка lib уже поддерживает вложенные фрагменты из Android 1.6+
FindOutIslamNow
13

Фрагменты могут быть добавлены внутрь других фрагментов, но тогда вам нужно будет удалять его из родительского фрагмента каждый раз, когда onDestroyView()вызывается метод родительского фрагмента. И снова добавьте его в onCreateView()метод родительского фрагмента .

Просто сделай так:

@Override
    public void onDestroyView()
    {
                FragmentManager mFragmentMgr= getFragmentManager();
        FragmentTransaction mTransaction = mFragmentMgr.beginTransaction();
                Fragment childFragment =mFragmentMgr.findFragmentByTag("qa_fragment")
        mTransaction.remove(childFragment);
        mTransaction.commit();
        super.onDestroyView();
    }
Наполеон
источник
4
У меня это не сработало. У меня был фрагмент, который увеличивал представление, содержащее два дочерних представления. В onActivityCreated()него добавлены два фрагмента, по одному в каждое из представлений, используя getFragmentManager(). Это сработало в первый раз, но при вращении и возобновлении был виден только один из фрагментов. getChildFragmentManager()Вместо этого поведение было правильным . Предложенный здесь подход onSaveInstanceState()вызвал исключение, поскольку действие уже было вызвано. Фиксация транзакции с использованием commitAllowingStateLoss()позволила избежать исключения, но не решила исходную проблему.
user1978019
любые идеи об этом stackoverflow.com/questions/63052712/…
Сунил Чаудхари
9

Я решил эту проблему. Вы можете использовать библиотеку поддержки и ViewPager. Если вам не нужно смахивать жестами, вы можете отключить смахивание. Итак, вот код для улучшения моего решения:

public class TestFragment extends Fragment{
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.frag, container, false);
    final ArrayList<Fragment> list = new ArrayList<Fragment>();

    list.add(new TrFrag());
    list.add(new TrFrag());
    list.add(new TrFrag());

    ViewPager pager = (ViewPager) v.findViewById(R.id.pager);
    pager.setAdapter(new FragmentPagerAdapter(getChildFragmentManager()) {
        @Override
        public Fragment getItem(int i) {
            return list.get(i);
        }

        @Override
        public int getCount() {
            return list.size();
        }
    });
    return v;
}
}

PS Это уродливый код для тестирования, но он улучшает то, что возможно.

Фрагмент PPS Inside ChildFragmentManagerследует передать вViewPagerAdapter

Vetalll
источник
8

вы можете использовать getChildFragmentManager()функцию.

пример:

Родительский фрагмент:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    rootView = inflater.inflate(R.layout.parent_fragment, container,
            false);


    }

    //child fragment 
    FragmentManager childFragMan = getChildFragmentManager();
    FragmentTransaction childFragTrans = childFragMan.beginTransaction();
    ChildFragment fragB = new ChildFragment ();
    childFragTrans.add(R.id.FRAGMENT_PLACEHOLDER, fragB);
    childFragTrans.addToBackStack("B");
    childFragTrans.commit();        


    return rootView;
}

Родительский макет ( parent_fragment.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"
    android:background="@android:color/white">



    <FrameLayout
        android:id="@+id/FRAGMENT_PLACEHOLDER"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>




</LinearLayout>

Дочерний фрагмент:

public class ChildFragment extends Fragment implements View.OnClickListener{

    View v ;
    @Override
    public View onCreateView(LayoutInflater inflater,
                             @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        View rootView = inflater.inflate(R.layout.child_fragment, container, false);


        v = rootView;


        return rootView;
    }



    @Override
    public void onClick(View view) {


    }


} 
С.М. Эмамян
источник
4

Ничего сложного. Мы не можем использовать getFragmentManager()здесь. Для использования фрагментов внутри фрагмента мы используем getChildFragmentManager(). Остальное будет так же.

Виджей Шарма
источник
3

Вы можете добавить FrameLayoutк фрагменту и заменить его другим фрагментом при его инициализации.

Таким образом, вы можете считать, что другой фрагмент находится внутри первого фрагмента.

ЛюбовьБольшойПицца
источник
2

В настоящее время вложенные фрагменты поддерживаются только в том случае, если они созданы программно! Таким образом, в настоящее время в схеме макета xml не поддерживается макет вложенных фрагментов!

Андреа Беллитто
источник
1

Это может помочь тем, кто работает на Kotlin, вы можете использовать функцию расширения, поэтому создайте файл kotlin, скажем, «util.kt», и добавьте этот фрагмент кода.

fun Fragment.addChildFragment(fragment: Fragment, frameId: Int) {

    val transaction = childFragmentManager.beginTransaction()
    transaction.replace(frameId, fragment).commit()
}

Допустим, это класс ребенка

class InputFieldPresentation: Fragment()
{
    var views: View? = null
    override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        views = inflater!!.inflate(R.layout.input_field_frag, container, false)
        return views
    }

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
    }
    ...
}

Теперь вы можете добавить детей к отцовскому фрагменту следующим образом

 FatherPresentation:Fragment()
{
  ...

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val fieldFragment= InputFieldPresentation()
        addChildFragment(fieldFragment,R.id.fragmet_field)
    }
...
}

где R.id.fragmet_field - это идентификатор макета, который будет содержать фрагмент. Разумеется, этот текст находится внутри исходного фрагмента. Вот пример

Father_fragment.xml :

<LinearLayout android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    >

    ...

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:id="@+id/fragmet_field"
            android:orientation="vertical"
            >
        </LinearLayout>
    ...

    </LinearLayout>
ДИНА ТАКЛИТ
источник
1

MapFragmentКоманда Android говорит, что поддержки нет , начиная с Android 3.0. Здесь больше информации о проблеме. Но что вы можете сделать, создав фрагмент, который возвращает MapActivity. Вот пример кода. Спасибо inazaruk:

Как это работает :

  • MainFragmentActivity - это действие, которое расширяет FragmentActivityи размещает два MapFragment.
  • MyMapActivity расширяет MapActivity и имеет MapView.
  • LocalActivityManagerFragment хосты LocalActivityManager.
  • MyMapFragment расширяется LocalActivityManagerFragmentи с помощью TabHostсоздает внутренний экземпляр MyMapActivity.

Если у вас есть сомнения, дайте мне знать

раллат
источник
0

Привет, я решил эту проблему, поместив каждый фрагмент в отдельный макет. Я сделал видимым только связанный макет и убрал другие видимости.

Я имею в виду:

<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">

     <LinearLayout android:id="@+id/linearLayout1"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:orientation="horizontal">
           <Button android:layout_width="wrap_content"
                   android:id="@+id/button1"
                   android:layout_height="wrap_content"
                   android:text="Button1"></Button>
           <Button android:text="Button2"
                   android:id="@+id/button2"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"></Button>
           <Button android:text="Button3"
                   android:id="@+id/button3"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"></Button>
           <Button android:text="Button4"
                   android:id="@+id/button4"
                   android:layout_width="wrap_content"
                   android:layout_height="wrap_content"></Button>
   </LinearLayout>
   <LinearLayout android:layout_width="full_screen"
              android:layout_height="0dp"
              android:layout_weight="1"
              android:id="action_For_Button1"
              android:visibility="visible">
                    <Fragment android:layout_width="full_screen"
                              android:layout_height="full_screen"
                              android:id="fragment1"
                                        .
                                        .
                                        .
             / >
     </LinearLayout>

     <LinearLayout android:layout_width="full_screen"
              android:layout_height="0dp"
              android:id="action_For_Button1"
              android:layout_weight="1"
              android:visibility="gone">
                       <Fragment android:layout_width="full_screen"
                                 android:layout_height="full_screen"
                                 android:id="fragment2"
                                           .
                                           .
                                           .
             / >
        </LinearLayout>
                     .
                     .
                     .
        </LinearLayout>

Я предполагал, что вы откроете свою страницу при нажатии кнопки 1. Вы можете управлять видимостью вашего фрагмента при нажатии кнопки. Вы можете сделать видимым связанный макет, а другие исчезнуть, а с помощью диспетчера фрагментов вы можете взять свой фрагмент. Этот подход сработал для меня. И поскольку представление, которое имеет видимость: ушел, невидимо и не занимает места для макета, я думаю, что этот подход не вызывает проблем с пространством.

PS: Я просто пытался объяснить, что в моем коде решения могут быть синтаксические ошибки или неполная структура.

Севиль
источник