FragmentPagerAdapter — как обрабатывать изменения ориентации?

Я использую FragmentPagerAdapter, содержащий несколько фрагментов, которые нужно уведомлять об изменениях в database. Я делаю это, перебирая фрагменты, реализующие интерфейс обратного вызова, и вызывая метод refreshData().

Это работает нормально, пока устройство не изменит ориентацию. После изменения ориентации пользовательский интерфейс фрагмента не обновляется, хотя вызов метода работает.

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

private class DataPagerAdapter extends FragmentPagerAdapter {

    private DataFragment[] fragments = new DataFragment[] {
                                     new FooDataFragment(), BarDataFragment()};

    public DataPagerAdapter(android.support.v4.app.FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return fragments[position];
    }

    @Override
    public int getCount() {
        return fragments.length;
    }

    public DataFragment[] getFragments() {
        return fragments;
    }
}

protected void refreshData() {
    for (DataFragment dataFragment : mPagerAdapter.getFragments()) {
     dataFragment.refreshData();
}

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


person Philipp E.    schedule 13.07.2013    source источник
comment
добавьте это в свой тег активности в манифесте android:configChanges=orientation|screenSize|keyboardHidden (целевой API 13+)   -  person TheFlash    schedule 13.07.2013
comment
minSdkVersion - 7, так что это не сработает для меня   -  person Philipp E.    schedule 13.07.2013


Ответы (4)


Да, как вы сказали, FragmentManager обрабатывает фрагменты после изменения ориентации, поэтому getItem в адаптере не вызывается. Но вы можете переопределить метод instantiateItem(), который вызывается даже после изменения ориентации, и привести объект к фрагменту и сохранить его в своем массиве.

 @Override
 public Object instantiateItem(ViewGroup container, int position) {
     DataFragment fragment = (DataFragment) super.instantiateItem(container, position);
     fragments[position] = fragment;
     return fragment;
 }
person Koso    schedule 13.07.2013
comment
Это выглядит аккуратно, к сожалению, поведение остается прежним. Я также попытался добавить android:configChanges=orientation в манифест. - person Philipp E.; 13.07.2013
comment
Я действительно не рекомендую вам использовать orientantionChanges в манифесте. Итак, вы говорите, что refreshData() все еще вызывается для старых фрагментов? Когда вы вызываете refreshData()? Вы должны проверить, действительно ли вызывается instanceItem() и вызывается ли он перед методом refreshData(). - person Koso; 13.07.2013
comment
Вы правы, не называется. Странный. Какие-либо предложения? RefreshData() вызывается намного позже создания экземпляра фрагмента, например, когда пользователь нажимает кнопку, но он также может запускаться асинхронно фоновой службой. - person Philipp E.; 13.07.2013
comment
а вы удалили android:configChanges=orientation?? - person Koso; 13.07.2013
comment
О, забыл упомянуть об этом. Да, я сделал. - person Philipp E.; 13.07.2013
comment
Извините за поздний ответ, чувак. Я обновил код ответа. Вам нужно использовать instantiateItem(ViewGroup container, int position), а не instantiateItem(View container, int position). Надеюсь, теперь это сработает. - person Koso; 13.07.2013
comment
@koso, потратив 5 часов на его решение, ваше решение было чертовски правильным! - person r4m; 14.11.2013
comment
Метод создания имени фрагмента довольно прост — я нашел его на Grepcode: return "android:switcher:" + viewId + ":" + index; viewId — идентификатор содержащего представления, а index — позиция фрагмента. Используя это, я смог вытащить фрагменты из FragmentManager. - person bjdodson; 15.10.2014
comment
Потратив 1-2 дня на сохранение экземпляра фрагмента гнезда внутри viewpager с помощью FragmentStatePagerAdapter, это решение устранило мою проблему. - person Rajen Raiyarela; 26.03.2015

Не могли бы вы просто установить флаг android:configChanges в своей активности в AndroidManifest.xml, чтобы предотвратить воссоздание активности?

<activity
    android:name="com.example.test.activity.MainActivity"
    android:configChanges="orientation|screenSize|keyboardHidden"/>

также там вы можете найти больше возможностей: https://android.jlelse.eu/handling-orientation-changes-in-android-7072958c442a

person Kamil Z    schedule 15.02.2019

Я воссоздал адаптер пейджера в методе onResume:

override fun onResume() {
    super.onResume()
    val viewPager = findViewById<ViewPager>(R.id.my_view_pager)
    viewPager.adapter = MyPagerAdapter(supportFragmentManager)
}
person Trieu Doan    schedule 05.10.2019

У меня была похожая проблема, мое рабочее решение: в действии, где вызов адаптера adapter.notifyDataSetChanged();

переопределить метод getItemPostion() в вашем адаптере

@Override
    public int getItemPosition(Object object) {
        MyFragment frag = (MyFragment)object;
        frag.refresh();
        return POSITION_UNCHANGED;
    }

мой FragmentStatePagerAdapater (мой адаптер) содержит только один тип фрагмента, но я думаю, что вы могли бы использовать instanceof (для большего количества типов фрагментов), а затем использовать приведение или каждый фрагмент будет реализовывать интерфейс с методом refresh()

Затем добавьте метод refresh() в свои фрагменты и используйте эту реализацию:

public void refresh() {
        LayoutInflater inflater = LayoutInflater.from(getActivity());
        ViewGroup viewGroup = (ViewGroup) getView();
        viewGroup.removeAllViewsInLayout();
        View view = onCreateView(inflater, viewGroup, null);
        viewGroup.addView(view);

    }

Это сработало для меня. Мое мнение: Проблема возникла, когда устройство меняет ориентацию, адаптер уничтожается, но фрагменты все еще находятся в памяти, и система пытается использовать их повторно после изменения ориентации, но представления, содержащиеся во фрагментах, также уничтожаются, а фрагментам нужны для «воссоздания» (вызов метода onCreateView). Может я ошибаюсь, так что поправьте меня... .

(Извините за мой английский )

person Pauli    schedule 13.10.2014
comment
Это решение не подходит для большинства приложений. Бывают случаи, когда андроид повторно создаст ваш фрагмент от вашего имени в качестве оптимизации (например, после изменения ориентации). Это не всегда будет приводить к желаемым результатам — например, если вы добавите свой ViewPager вне действия onCreate, фрагмент получит нулевой контейнер. Чтобы исправить такие проблемы, вы можете удалить фрагмент в onCreateView() { if(container == null) { removeMeFromFragmentManager(); вернуть ноль; ... }. Ваш новый FragmentPageAdapter теперь будет воссоздавать этот экземпляр фрагмента. - person dzlatkov; 05.04.2015