ViewPager внутри фрагмента теряет свое содержимое при повторном отображении фрагмента

У меня есть простой фрагмент с ViewPager.

Я использую обновленную библиотеку поддержки v4 rev18!

Если я покажу его в первый раз, все работает нормально, если я вернусь и покажу его снова, приложение вылетит со следующим исключением:

java.lang.NullPointerException
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:569)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:211)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1281)
at android.view.View.dispatchRestoreInstanceState(View.java:12043)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2688)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2694)
at android.view.View.restoreHierarchyState(View.java:12021)
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:425)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:949)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:440)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4800)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:798)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:565)
at dalvik.system.NativeStart.main(Native Method)

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

public class RoutineDayFragment extends BaseFragment
{
    private RoutinesActivity mParent = null;

    @InjectView(R.id.pager)
    MyViewPager pager = null;
    @InjectView(R.id.indicator)
    PagerSlidingTabStrip indicator = null;

    private FragmentStatePagerAdapter mAdapter = null;

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        mParent = ((RoutinesActivity) getBaseActivity());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View v = inflater.inflate(R.layout.view_pager, container, false);
        Views.inject(this, v);

        mAdapter = new FragmentStatePagerAdapter(getChildFragmentManager())
        {
            @Override
            public int getCount()
            {
                if (mParent.getSharedData().selectedRoutine == -1)
                    return 0;
                return mParent.getSharedData().routines.get(mParent.getSharedData().selectedRoutine).getRWorkoutDay().size();
            }

            @Override
            public Fragment getItem(int pos)
            {
                return DayFragment.newInstance(pos);
            }

            @Override
            public CharSequence getPageTitle(int pos)
            {
                return Common.getString(R.string.day) + (pos + 1);
            }
        };

        pager.setAdapter(mAdapter);
        indicator.setViewPager(pager);
        return v;
    }
}

person prom85    schedule 09.09.2013    source источник
comment
См. stackoverflow.com/a/17738748/794088 . даже если вы используете версию 18 библиотеки поддержки, вам все равно нужно будет учитывать устройства, которые все еще имеют более старые версии кода FragmentManager с ошибками (все, что ниже 4.2). ХТХ!   -  person petey    schedule 09.09.2013
comment
Я уже пробовал этот трюк, но это не помогло... другой момент, касающийся старых устройств... Я почти уверен, что это неправда, библиотека поддержки является частью моего приложения и будет поставляться с ним. .. Так что версия, которую я использую, единственная, которая имеет значение...   -  person prom85    schedule 09.09.2013
comment
Это по-прежнему будет происходить на устройствах, которые не используют класс поддержки fragmentmanager вместо того, который входит в состав ОС 3.2–4.1.1.   -  person petey    schedule 09.09.2013


Ответы (1)


Используйте AsyncTask для установки ViewPagerAdapter:

private class SetAdapterTask extends AsyncTask<Void, Void, Void> {
        protected Void doInBackground(Void... params) {
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {

            if(mAdapter != null) mViewPager.setAdapter(mAdapter);
        }
    }   

Назовите это так:

mAdapter = new PageAdapter(getChildFragmentManager());
new SetAdapterTask().execute();

И сбросьте адаптер в методе onResume() вашего фрагмента.

ОБНОВЛЕНИЕ. Как вложить ViewPager внутрь фрагмента?

Хорошо, вот оно. Я изменил пример Google для эффективной навигации. чтобы соответствовать вашим заботам.

Что я создал?

  • Я создал простое приложение, содержащее MainActivity с ViewPager и 3 вкладками.
  • Каждая из этих вкладок представлена ​​фрагментом, который также содержит ViewPager.
  • ViewPager внутри фрагмента содержит 10 страниц.
  • Итак, у нас есть 3 «основных» вкладки/фрагмента, в каждой из которых еще по 10 фрагментов.
  • В демонстрационных целях я сделал верхний уровень ViewPager недоступным для пролистывания, поэтому вам нужно использовать вкладки для переключения между основными фрагментами (я создал CustomViewPager и внес некоторые изменения, чтобы удалить возможность прокрутки ViewPagers). )
  • ViewPager внутри основных фрагментов можно прокручивать, поэтому вы можете прокручивать, чтобы переключаться между вложенными фрагментами, и нажимать вкладки, чтобы переключаться между основными фрагментами.
  • Если щелкнуть вложенный фрагмент, запускается новое действие.
  • Если вы вернетесь к старой активности, когда новая активность будет закрыта, состояние Fragments и ViewPager будет сохранено.
  • При переключении main-Fragments их состояние также сохраняется

Вот Main Activity.java

Он содержит два адаптера, один для основных фрагментов, один для подфрагментов. Кроме того, это два класса фрагментов, один из которых содержит ViewPager (основной фрагмент), а другой - вложенный фрагмент (внутри вложенного ViewPager).

public class MainActivity extends FragmentActivity implements ActionBar.TabListener {

    AppSectionsPagerAdapter mAppSectionsPagerAdapter;
    NonSwipeableViewPager mViewPager;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager());

        // Set up the action bar.
        final ActionBar actionBar = getActionBar();
        actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        // Set up the ViewPager, attaching the adapter and setting up a listener for when the
        // user swipes between sections.
        mViewPager = (NonSwipeableViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mAppSectionsPagerAdapter);
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                actionBar.setSelectedNavigationItem(position);
            }
        });

        // For each of the sections in the app, add a tab to the action bar.
        for (int i = 0; i < mAppSectionsPagerAdapter.getCount(); i++) {
            actionBar.addTab(
                    actionBar.newTab()
                            .setText(mAppSectionsPagerAdapter.getPageTitle(i))
                            .setTabListener(this));
        }
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {}

    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
        // When the given tab is selected, switch to the corresponding page in the ViewPager.
        mViewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {}

    public static class AppSectionsPagerAdapter extends FragmentPagerAdapter {

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

        @Override
        public Fragment getItem(int i) {
            switch (i) {

                default:
                    // The other sections of the app are dummy placeholders.
                    Fragment fragment = new ViewPagerContainerFragment();
                    return fragment;
            }
        }

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

        @Override
        public CharSequence getPageTitle(int position) {
            return "Tab " + (position +1);
        }
    }

    public static class ViewPagerAdapter extends FragmentPagerAdapter {

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

        @Override
        public Fragment getItem(int i) {
            switch (i) {

                default:
                    // The other sections of the app are dummy placeholders.
                    Fragment fragment = new ViewPagerFragment();
                    Bundle b = new Bundle();
                    b.putString("key", "I am fragment nr " + i);
                    fragment.setArguments(b);
                    return fragment;
            }
        }

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

    /**
     * THIS FRAGMENT CONTAINS A VIEWPAGER
     */
    public static class ViewPagerContainerFragment extends Fragment {

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

            ViewPager pager = (ViewPager) rootView.findViewById(R.id.nestedViewPager);

            pager.setAdapter(new ViewPagerAdapter(getChildFragmentManager()));

            return rootView;
        }
    }

    /**
     * THIS FRAGMENT IS INSIDE THE VIEWPAGER
     */
    public static class ViewPagerFragment extends Fragment {

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

            ((TextView) rootView.findViewById(R.id.tv1)).setText(getArguments().getString("key"));
            ((TextView) rootView.findViewById(R.id.tv1)).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(getActivity(), CollectionDemoActivity.class);
                    startActivity(intent);
                }
            });

            return rootView;
        }
    }
}

activity_main.xml

<com.example.android.effectivenavigation.NonSwipeableViewPager 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

main_fragment.xml

<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nestedViewPager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="24sp" />

sub_fragment.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="50sp" />

Конечный результат выглядит так:

У нас есть 3 фрагмента верхнего уровня, каждый из которых содержит ViewPager с 10 фрагментами.

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

person Philipp Jahoda    schedule 09.09.2013
comment
Я уже пытался использовать обработчик, но я попробую и это. Хотя, насколько я читал, это не должно быть проблемой с ChildFragmentManager, только если я использую вложенные фрагменты без ChildFragmentManager... Но я попробую это... - person prom85; 09.09.2013
comment
Вы должны использовать ChildFragmentManager. Также попробуйте установить его в методе onResume() фрагмента. - person Philipp Jahoda; 09.09.2013
comment
Хорошо. Я думал, что это уже исправит. К счастью, у меня есть рабочее решение, использующее Fragments и вложенный Viewpager. Дайте мне минут просмотра, чтобы закодировать пример. - person Philipp Jahoda; 09.09.2013
comment
хорошо, вы прочитали мой ответ, прежде чем я смог удалить его и попробовать вторую подсказку ... но теперь я тестирую оба, используя асинхронную задачу, и безуспешно сбрасываю ее в onResume ... - person prom85; 09.09.2013
comment
Я обновил свой ответ полным примером. Я также могу отправить вам демонстрационный проект, если хотите. - person Philipp Jahoda; 09.09.2013
comment
Я проверю это через несколько минут... пока, я вижу одно, вы используете ТОЛЬКО FragmentPagerAdapter, это означает, что фрагмент не уничтожается... все фрагменты будут в памяти, пока существует активность... но я также не заставил свое приложение работать с этим адаптером, поэтому, если это сработает, это может помочь и мне... по крайней мере, спасибо за усилия, я проверю это... - person prom85; 09.09.2013
comment
Добро пожаловать. Я только что протестировал приложение с помощью FragmentStatePagerAdapter — оно тоже отлично работает. - person Philipp Jahoda; 09.09.2013
comment
У меня все еще есть проблемы, вы можете взглянуть на мой более подробный новый вопрос, если у вас есть время... спасибо за помощь, хотя... это просто не работает, никак, что бы я ни делал... Если Я использую подфрагменты вручную... я создал полный пример на основе того, который вы мне дали... stackoverflow.com/questions/18706941/ - person prom85; 10.09.2013
comment
Где находится com.example.android.efficientnavigation.NonSwipeableViewPager? - person Chris Sprague; 03.12.2014
comment
Вот и все: -s" title="как отключить пейджинг, проводя пальцем в viewpager, но при этом иметь возможность s">stackoverflow.com/questions/9650265/ - person Philipp Jahoda; 03.12.2014
comment
@Philipp Jahoda Просто хотел поблагодарить вас за этот отличный пример. Мне нужно будет потратить некоторое время на его анализ, чтобы выяснить, как именно он применим к моему варианту использования, но я считаю, что вы достигли высоких точек (кстати: мне нужно было сделать некоторые настройки, чтобы заставить его работать в последней версии Android Studio/Gradle ). - person Chris Marshall; 30.07.2015