Подтвердить что ты не робот

Фрагменты ViewPager не воссоздаются после FragmentTransaction.replace(), а затем кнопки возврата

Я пытаюсь реализовать сохранение и восстановление состояния, но у меня возникают проблемы при замене основного Fragment на PreferenceFragment, а затем нажатие кнопки "Назад" . Мой основной Fragment состоит из ViewPager с FragmentPagerAdapter с 3 Fragments для прокрутки. Ни один из Fragment.onCreateView() обратных вызовов для моего 3 Fragment не вызывается после нажатия кнопки "Назад" . Я пробовал все решения, которые я пришел сюда, на SO, но я не смог решить проблему.

Еще одна важная вещь, которую следует отметить, состоит в том, что данные для моего 3 ViewPager Fragment хранятся в отдельных классах, которые инстанцируются и доступны через Activity. Все 3 Fragment содержат RecyclerView для перечисления большого количества данных. Это делается для чистоты, но также для сохранения этих данных в Activity. Это, вероятно, не проблема, так как она отлично работает при запуске приложения, но также и потому, что главная проблема заключается в том, что мои Fragments не воссозданы.

Неожиданное поведение:

При создании приложения все работает нормально, но когда я заменю основной Fragment (содержащий ViewPager с FragmentPagerAdapter) другим, а затем нажав кнопку возврата, Fragment в ViewPager не воссозданы. Вызывается onCreateView() моего основного Fragment.

Вопросы:

Что мне не хватает? Должны ли быть созданы другие обратные вызовы? Где и как мне воссоздать Fragments?

Что я пробовал:

  • Изменено использование FragmentAdapter, но я должен использовать getSupportFragmentManager(), как это было в здесь.

EDIT: использование ошибочных FragmentManager на самом деле было источником моих проблем. См. Мой ответ ниже.

  1. Добавить @Override public int getItemPosition(Object object) {return POSITION_NONE;} к FragmentPagerAdapter, как предложено здесь.
  2. Измените FragmentPagerAdapter на FragmentStatePagerAdapter, что не имеет значения здесь, насколько я понимаю. Внутренние элементы FragmentStatePagerAdapter фактически сохраняют Fragments, но они не отображаются заново.
  3. Добавьте основной Fragment назад с помощью FragmentTransaction, когда вызывается onCreateView() и в нескольких других обратных вызовах этого Fragment, см. ниже MMMainFragment.
  4. Различные вещи.

Мой класс MainFragment с соответствующим XML-макетом

public class MMMainFragment extends Fragment
{
    private MMViewPager mMMViewPager = null;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        // Note that this method IS called when the back button is pressed.
        // I have tried setting the content back to this instance in several places.

        View view = inflater.inflate(R.layout.mm_main_fragment, container, false);

        // Setup ViewPager.
        mMMViewPager = (MMViewPager) view.findViewById(R.id.mm_pager);
        TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout);
        tabLayout.setupWithViewPager(mMMViewPager);

        return view;
    }

    public MMViewPager getMMViewPager()
    {
        return mMMViewPager;
    }
}

mm_main_fragment.xml

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

    <android.support.design.widget.TabLayout
        android:id="@+id/mm_tablayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabMode="scrollable" />

    <com.mycompany.myapp.gui.mmpager.MMViewPager
        android:id="@+id/mm_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1" />


</LinearLayout>

Тогда в Activity.onCreate() я просто выполните:

// Insert Main Fragment.
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
        .replace(R.id.mm_content_frame, new MMMainFragment())
        .commit();

где mm_content_frame является FrameLayout, где я заменяю и удаляю Fragment s. Затем, когда кнопка для просмотра настроек нажата, я запускаю следующий фрагмент ниже в Activity. Я добавляю этот Fragment в backstack, чтобы иметь возможность использовать кнопку "Назад" .

public void showSettingsFragment()
{
    getSupportActionBar().setTitle(getString(R.string.mm_drawer_settings));
    getSupportFragmentManager().beginTransaction()
            .replace(R.id.mm_content_frame, new MMPreferencesFragment(), FragmentConstants.SETTINGS_FRAGMENT_TAG)
            .addToBackStack(null)
            .commit();
}

Клавиши MMViewPager:

public class MMViewPager extends ViewPager
{
    private MMActivity mMMActivity;
    private MMPagerAdapter mMMPagerAdapter;

    public MMViewPager(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        mMMActivity = (MMActivity) context;
        mMMPagerAdapter = new MMPagerAdapter(mMMActivity.getSupportFragmentManager(), mMMActivity);

        this.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
        this.setAdapter(mMMMPagerAdapter);
        this.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES);
    }
}

Класс MMPagerAdapter, в настоящее время FragmentStatePagerAdapter:

public class MMPagerAdapter extends FragmentStatePagerAdapter
{
    private MMActivity mMMActivity;

    public MMPagerAdapter(FragmentManager fragmentManager, MMActivity mmActivity)
    {
        super(fragmentManager);
        mMMActivity = mmActivity;
    }

    @Override
    public Fragment getItem(int position)
    {
        switch (position)
        {
            case PagerConstants.PAGE_FILTER_RECIPES:
                return new FilteredRecipesFragment();

            case PagerConstants.PAGE_SELECTED_RECIPES:
                return new SelectedRecipesFragment();

            case PagerConstants.PAGE_SHOPPING_LIST:
                return new ShoppingListFragment();

            default:
                return null;
        }
    }

    @Override
    public int getCount()
    {
        return PagerConstants.NUMBER_OF_PAGES; // 3
    }

    @Override
    public CharSequence getPageTitle(int position)
    {
        switch (position)
        {
            case PagerConstants.PAGE_FILTER_RECIPES:
                return mMMActivity.getResources().getString(R.string.mm_title_recipe_filter_fragment);

            case PagerConstants.PAGE_SELECTED_RECIPES:
                return mMMActivity.getResources().getString(R.string.mm_title_selected_recipes_fragment);

            case PagerConstants.PAGE_SHOPPING_LIST:
                return mMMActivity.getResources().getString(R.string.mm_title_shopping_list_fragment);

            default:
                return "Tab";
        }
    }
}

Для справки, здесь также основной макет Activity:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/mm_drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Source: http://developer.android.com/training/implementing-navigation/nav-drawer.html -->
    <!-- The main content view -->
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Source: http://android-developers.blogspot.in/2014/10/appcompat-v21-material-design-for-pre.html -->
        <android.support.v7.widget.Toolbar
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:id="@+id/mm_toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:minHeight="?attr/actionBarSize"
            android:layout_alignParentTop="true"
            style="@style/MMActionBar"
            app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        <!-- The FrameLayout where I replace Fragments -->
        <FrameLayout
            android:id="@+id/mm_content_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_below="@id/mm_toolbar"/>

    </RelativeLayout>


    <ListView
        android:id="@+id/mm_drawer_listview"
        android:layout_width="260dp"
        android:layout_height="match_parent"
        android:paddingLeft="16dp"
        android:paddingRight="16dp"
        android:paddingStart="16dp"
        android:layout_gravity="start"
        android:choiceMode="singleChoice"
        android:divider="@android:color/transparent"
        android:dividerHeight="0dp"
        android:background="@color/mm_white" />

</android.support.v4.widget.DrawerLayout>
4b9b3361

Ответ 1

После борьбы с этим в течение нескольких дней я понял, что проблема в том, что я передал неправильный FragmentManager в ViewPager. Это действительно FragmentManager, возвращаемый Fragment.getChildFragmentManager(), который должен использоваться. Это имеет смысл, так как сам Fragment хранит состояние дочернего элемента Fragment в ViewPager. Я не уверен, почему я не мог выполнить эту работу раньше, но теперь LifeCycle приложения отлично работает со следующей настройкой в ​​моем основном методе Fragment onCreate():

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
    View view = inflater.inflate(R.layout.mm_main_fragment, container, false);

    mMMActivity = (MMActivity) container.getContext();
    mMMActivity.getSupportActionBar().setTitle(getString(R.string.mm_toolbar_title_main_fragment));

    // Setup ViewPager.
    mViewPager = (ViewPager) view.findViewById(R.id.mm_pager);
    mAdapter = new MMPagerAdapter(getChildFragmentManager(), mMMActivity); // <-- This is the key
    mViewPager.setAdapter(mAdapter);
    mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
    mViewPager.setCurrentItem(PagerConstants.PAGE_FILTER_RECIPES);

    // Setup TabLayout.
    TabLayout tabLayout = (TabLayout) view.findViewById(R.id.mm_tablayout);
    tabLayout.setupWithViewPager(mViewPager);

    return view;
}

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

Приложение для тестирования:

В исследовании этой проблемы я создал тестовое приложение для этого. Те, кто заинтересован, могут клонировать его из здесь. У него уродливые цвета.

Ответ 2

Насколько я понимаю, вы решили свою проблему. В прошлом я столкнулся с аналогичной проблемой. Может быть, это поможет другим попробовать что-то подобное.

Вещь, которая помогла мне сохранить состояние фрагмента, заключалась в замене

@Override
public Fragment getItem(int position)
{
    switch (position)
    {
        case PagerConstants.PAGE_FILTER_RECIPES:
            return new FilteredRecipesFragment();

        case PagerConstants.PAGE_SELECTED_RECIPES:
            return new SelectedRecipesFragment();

        case PagerConstants.PAGE_SHOPPING_LIST:
            return new ShoppingListFragment();

        default:
            return null;
    }
}

моего адаптера.

Я изменяю Adapter, чтобы сохранить HashMap в Fragment. Таким образом, фрагменты создаются один раз, а затем извлекаются из HashMap. В зависимости от вашей структуры вы можете использовать ArrayList вместо HashMap.

Это делается эффективным и "умным" способом реализации getItemPosition(Object object) - return POSITION_NONE; или правильной позиции элемента.

Я действительно не помню, имел ли я getChildFragmentManager в адаптере или нет.

Я думаю, что я мог бы вникнуть в свою файловую систему, если вам нужна дополнительная информация:)