Просмотр фрагмента ViewPager потерян, когда родительский фрагмент ViewPager скрыт, а затем показан

Я видел странное поведение с моим ViewPager вместе со своим собственным FragmentStatePagerAdapter.

Иерархия My View выглядит следующим образом:

-> (1) Fragment root view (RelativeLayout)
 -> (2) ViewPager
  -> (3) ViewPager current fragment view

Когда фрагмент, отвечающий за корневой вид фрагмента (1), скрывается (используя операцию hid() в транзакции фрагмента), а затем отображается (с .show()), представление фрагмента, которое в настоящее время отображается в ViewPager (3) становится нулевым, хотя фрагмент все еще существует. В принципе, мой ViewPager становится полностью пустым/прозрачным.

Единственное, что я нашел, чтобы исправить это, - это позвонить

int current = myViewPager.getCurrentItem();
myViewPager.setAdapter(myAdapter);
myViewPager.setCurrentItem(current);

после отображения родительского фрагмента. Это как-то заставляет взгляды воссоздаваться и появляться на экране. К сожалению, это иногда вызывает исключения, связанные с адаптером пейджера, вызывающим unregisterDataSetObserver() дважды на старого наблюдателя.

Есть ли лучший способ сделать это? Я предполагаю, что я спрашиваю:

Почему мои фрагментные представления внутри моего ViewPager будут уничтожены, если родительский фрагмент ViewPager скрыт?

Обновление: это также происходит, когда приложение "сведено к минимуму", а затем "восстанавливается" (путем нажатия клавиши домашнего действия и последующего возврата).

По запросу, здесь мой класс адаптера пейджера:

public class MyInfoSlidePagerAdapter extends FragmentStatePagerAdapter {

    private ArrayList<MyInfo> infos = new ArrayList<MyInfo>();

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

    public MyInfoSlidePagerAdapter (FragmentManager fm, MyInfo[] newInfos) {
        super(fm);
        setInfos(newInfos);
    }

    @Override
    public int getItemPosition(Object object) {
        int position = infos.indexOf(((MyInfoDetailsFragment)object).getMyInfo());
        return position > 0 ? position : POSITION_NONE;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return infos.get(position).getName();
    }

    @Override
    public Fragment getItem(int i) {
        return infos.size() > 0 ? MyInfoDetailsFragment.getNewInstance(infos.get(i)) : null;
    }

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

    public Location getMyInfoAtPosition(int i) {
        return infos.get(i);
    }

    public void setInfos(MyInfo[] newInfos) {
        infos = new ArrayList<MyInfo>(Arrays.asList(newInfos));
    }

    public int getPositionOfMyInfo(MyInfo info) {
        return infos.indexOf(info);
    }
}

Я переименовал некоторые переменные, но кроме этого, это именно то, что у меня есть.

Ответ 1

Вы не предоставляете достаточную информацию для своей конкретной проблемы, поэтому я создал образец проекта, который пытается воспроизвести вашу проблему: приложение имеет активность, которая содержит фрагмент (PagerFragment) в относительном макете и ниже этого макета У меня есть кнопка, которая скрывается и показывает выше PagerFragment. PagerFragment имеет ViewPager, и каждый фрагмент в адаптере пейджера просто отображает метку - этот фрагмент называется DataFragment. Список меток создается в родительской активности и передается в PagerFragment, а затем через адаптер для каждого DataFragment. Изменение видимости PagerFragment выполняется без проблем, и каждый раз, когда он становится видимым снова, он показывает предыдущую показанную метку.

Ключ проблемы: Используйте Fragment # getChildFragmentManager() при создании адаптера viewpager, а не getFragmentManager!

Возможно, вы можете сравнить этот простой проект с тем, что у вас есть, и проверить, где различия. Итак, здесь идет (сверху вниз):

PagerActivity (единственное действие в проекте):

public class PagerActivity extends FragmentActivity {

    private static final String PAGER_TAG = "PagerActivity.PAGER_TAG";

    @Override
    protected void onCreate(Bundle savedInstance) {
        super.onCreate(savedInstance);
        setContentView(R.layout.pager_activity);
        if (savedInstance == null) {
            PagerFragment frag = PagerFragment.newInstance(buildPagerData());
            FragmentManager fm = getSupportFragmentManager();
            fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
        }
        findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                changeFragmentVisibility();
            }
        });
    }

    private List<String> buildPagerData() {
        ArrayList<String> pagerData = new ArrayList<String>();
        pagerData.add("Robert de Niro");
        pagerData.add("John Smith");
        pagerData.add("Valerie Irons");
        pagerData.add("Metallica");
        pagerData.add("Rammstein");
        pagerData.add("Zinedine Zidane");
        pagerData.add("Ronaldo da Lima");
        return pagerData;
    }

    protected void changeFragmentVisibility() {
        Fragment frag = getSupportFragmentManager().findFragmentByTag(PAGER_TAG);
        if (frag == null) {
            Toast.makeText(this, "No PAGER fragment found", Toast.LENGTH_SHORT).show();
            return;
        }
        boolean visible = frag.isVisible();
        Log.d("APSampler", "Pager fragment visibility: " + visible);
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        if (visible) {
            ft.hide(frag);
        } else {
            ft.show(frag);
        }
        ft.commit();
        getSupportFragmentManager().executePendingTransactions();
    }
}

его файл макета pager_activity.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="4dp" >

    <Button
        android:id="@+id/btnFragments"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Hide/Show fragments" />

    <RelativeLayout
        android:id="@+id/layout_fragments"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/btnFragments"
        android:layout_marginBottom="4dp" >
    </RelativeLayout>

</RelativeLayout>

Обратите внимание, что я добавляю PagerFragment, когда активна операция, и класс PagerFragment:

public class PagerFragment extends Fragment {

    private static final String DATA_ARGS_KEY = "PagerFragment.DATA_ARGS_KEY";

    private List<String> data;

    private ViewPager pagerData;

    public static PagerFragment newInstance(List<String> data) {
        PagerFragment pagerFragment = new PagerFragment();
        Bundle args = new Bundle();
        ArrayList<String> argsValue = new ArrayList<String>(data);
        args.putStringArrayList(DATA_ARGS_KEY, argsValue);
        pagerFragment.setArguments(args);
        return pagerFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        data = getArguments().getStringArrayList(DATA_ARGS_KEY);
    }

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

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        pagerData = (ViewPager) view.findViewById(R.id.pager_data);
        setupPagerData();
    }

    private void setupPagerData() {
        PagerAdapter adapter = new LocalPagerAdapter(getChildFragmentManager(), data);
        pagerData.setAdapter(adapter);
    }

}

его макет (только ViewPager, который принимает полный размер):

<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager_data"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

и его адаптер:

public class LocalPagerAdapter extends FragmentStatePagerAdapter {
    private List<String> pagerData;

    public LocalPagerAdapter(FragmentManager fm, List<String> pagerData) {
        super(fm);
        this.pagerData = pagerData;
    }

    @Override
    public Fragment getItem(int position) {
        return DataFragment.newInstance(pagerData.get(position));
    }

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

Этот адаптер создает DataFragment для каждой страницы:

public class DataFragment extends Fragment {
    private static final String DATA_ARG_KEY = "DataFragment.DATA_ARG_KEY";

    private String localData;

    public static DataFragment newInstance(String data) {
        DataFragment df = new DataFragment();
        Bundle args = new Bundle();
        args.putString(DATA_ARG_KEY, data);
        df.setArguments(args);
        return df;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        localData = getArguments().getString(DATA_ARG_KEY);
    }

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

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        view.findViewById(R.id.btn_page_action).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), localData, Toast.LENGTH_SHORT).show();
            }
        });
        ((TextView) view.findViewById(R.id.txt_label)).setText(localData);
    }
}

и DataFragment:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="4dp" >

    <Button
        android:id="@+id/btn_page_action"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Interogate" />

    <TextView
        android:id="@+id/txt_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textAppearance="?android:attr/textAppearanceLarge" />

</RelativeLayout>

Наслаждайтесь кодированием!

Ответ 2

возможно, это поможет mViewPager.setOffscreenPageLimit(5)

Задайте количество страниц, которые должны быть сохранены по обе стороны от текущей страницы в иерархии представлений в состоянии ожидания. Страницы за пределами этого предел будет воссоздан из адаптера, если это необходимо.

Это предлагается как оптимизация. Если вы заранее знаете номер страниц, которые вам нужно будет поддерживать или иметь механизмы ленивой загрузки в место на ваших страницах, настройка этой настройки может иметь воспринимаемой гладкостью анимации пейджинга и взаимодействия. Если у вас есть небольшое количество страниц (3-4), которые вы можете сохранить одновременно, меньше времени будет потрачено на макет для вновь созданных подтипов просмотра, так как страницы пользователя назад и вперед.

Вы должны держать этот предел низким, особенно если ваши страницы имеют сложный макеты. По умолчанию этот параметр равен 1.

Ответ 3

View Pager довольно непреклонен, сохраняя при этом свои фрагменты свежими и тем самым оптимизируя производительность, освобождая память, когда фрагмент не используется. Очевидно, что это действительно полезная черта в мобильной системе. Но из-за этого постоянного освобождения ресурсов фрагмент создается каждый раз, когда он получает фокус.

mViewPager.setOffscreenPageLimit(NUMBEROFFRAGMENTSCREENS);

Вот документация.

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

Ответ 4

У меня была та же проблема. В моем приложении (FragmentActivity) есть пейджер (ViewPager) с 3 кадрами. При прохождении между фрагментами они разрушаются и воссоздаются все время. На самом деле это не создает проблем в функциональности (ожидайте закрытых курсоров), но мне также было интересно узнать об этом вопросе.

Я не знаю, есть ли способ обхода поведения ViewPager, но я предлагаю иметь объект конфигурации (возможно, статический) и до уничтожения сохранить ваш объект myViewPager в объекте конфигурации.

public class App extends FragmentActivity {

    static MyData data;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         data = (MyData) getLastCustomNonConfigurationInstance();
         if (data == null) {
             data = new MyData();
             data.savedViewPager = myViewPager;
         } else {
             myViewPager = data.savedViewPager;
        }
    }

    @Override
    public Object onRetainCustomNonConfigurationInstance() {
        Log.d("onRetainCustomNonConfigurationInstance", "Configuration call");
        return data;
    }
}

public class MyData {
    public ViewPager savedViewPager;
}

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

Надеюсь, вы найдете мое предложение полезным!