Фрагмент в сохраненном экземпляре пейджера просмотра всегда равен нулю

Я знаю, что этот вопрос задавался раньше, но ни один из ответов, данных до сих пор, мне ничем не помог.

У меня есть viewpager, который заполнен фрагментами (android.support.v4.app.Fragment) из FragmentStatePagerAdapter . Некоторые из этих фрагментов содержат логику, которую необходимо сохранять при изменении ориентации, например отслеживание того, какой вид выбран в данный момент.

Однако, хотя я сохраняю рассматриваемые данные в onSaveInstanceState, saveInstanceState всегда имеет значение null. Я могу решить эту проблему, сохранив данные в статической переменной (что, поскольку у меня есть только один экземпляр каждого фрагмента, будет работать для меня), но я обнаружил, что это довольно уродливое решение, и должен быть правильный способ сделать это.

Это один из фрагментов, который не сохраняет свое состояние при вращении:

    public class PriceSelectFragment extends Fragment {

    private TableRow mSelected;
    private int mSelectedPos = 0;

    // newInstance constructor for creating fragment with arguments
    public static PriceSelectFragment newInstance() {
        PriceSelectFragment fragmentFirst = new PriceSelectFragment();
        return fragmentFirst;
    }

    public PriceSelectFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_price_select, container, false);
        TableLayout mTable = (TableLayout)view.findViewById(R.id.price_table);

        List<PriceGroup> mPriceGroups = ((MainActivity) getActivity()).getPriceGroups();

        int i = 0;
        for (final PriceGroup group : mPriceGroups) {
            //Create row from layout and access child TextViews
            TableRow r = (TableRow)inflater.inflate( R.layout.price_group, mTable, false);
            TextView size = (TextView)r.getChildAt(0);
            TextView dimension = (TextView)r.getChildAt(1);
            TextView weight = (TextView)r.getChildAt(2);
            TextView price = (TextView)r.getChildAt(3);

            //Populate row with PriceGroup Data
            size.setText(group.sizeIndicator);
            dimension.setText(String.format("%2.0fx%2.0fx%2.0f", group.length, group.width, group.height));
            weight.setText(Float.toString(group.weight));
            price.setText(Integer.toString(group.price));

            //Alternate background color every other row
            if (i % 2 == 0) {
                r.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_1));
            }
            else {
                r.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_2));
            }
            mTable.addView(r); // Add to table

            r.setTag(i);
            r.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    selectRow((TableRow) v);
                }
            });
            i++;
        }

        mSelected = (TableRow)view.findViewWithTag(mSelectedPos);
        selectRow(mSelected);

        return view;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("selected", mSelectedPos);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (savedInstanceState != null) {
            mSelectedPos = savedInstanceState.getInt("selected");
        }
    }

    private void selectRow(TableRow row) {
        if ((int) mSelected.getTag() % 2 == 0) {
            mSelected.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_1));
        }
        else {
            mSelected.setBackgroundDrawable(getResources().getDrawable(R.drawable.price_selector_2));
        }
        mSelected = row;
        mSelectedPos = (int) mSelected.getTag();
        mSelected.setBackgroundColor(getResources().getColor(R.color.light_blue));
    }


}

Как мне решить эту проблему, не сохраняя свои состояния в статических переменных?

Редактировать

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

Также мое приложение структурировано следующим образом:

  • MainActivity with NavigationDrawer
    • Fragment1
      • ViewPager
        • subfragment1 - subfragment5
    • Фрагмент2
    • Фрагмент3

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


person Nobbe    schedule 29.04.2015    source источник
comment
Пожалуйста, добавьте, как вы используете фрагменты в своей деятельности   -  person Zharf    schedule 29.04.2015
comment
Пейджер просмотра находится в другом фрагменте в навигационном ящике. Должен ли я добавить код для этого фрагмента или MainActivity?   -  person Nobbe    schedule 29.04.2015
comment
Сохраняете ли вы состояние фрагментов в методе onSaveInstanceState деятельности?   -  person Dreagen    schedule 29.04.2015
comment
Вы говорите мне, что я должен отправить данные состояния из всех моих пяти фрагментов в мою основную деятельность и сохранить их там? А потом вернуть их из моей активности во фрагменты? Я думал сделать это, но я надеялся, что будет более элегантное решение. Нет ли способа обрабатывать состояния локально в самих фрагментах? Или хотя бы в родительском фрагменте? Или ViewPager?   -  person Nobbe    schedule 29.04.2015
comment
Это не должно быть так уж сложно. Вы должны помнить, что когда вы поворачиваете свое устройство, активность, содержащая ваш фрагмент, воссоздается. Я опубликую ответ с более подробной информацией   -  person Dreagen    schedule 29.04.2015


Ответы (3)


В вашем Activity, на котором размещен ваш Fragment, вам нужно сохранить ссылку на фрагмент в файле Bundle.

Что-то вроде этого должно работать для вас

public void onCreate(Bundle savedInstanceState) {

    if (savedInstanceState != null) {
        //Restore your fragment instance
        fragment1 = getSupportFragmentManager().getFragment(
                    savedInstanceState, "fragment");
    }
}


protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    getSupportFragmentManager().putFragment(outState, "fragment", fragment1);
}

fragment1 - это экземпляр Fragment1, который вы упомянули в своем вопросе, который необходимо воссоздать.

Раньше я не делал этого с такой структурой, как ваша, но я бы начал так:

Я полагаю, что в onSaveInstanceState в вашем Fragment1 вам нужно будет сделать то же самое с каждым из фрагментов в вашем ViewPager. Затем в onCreateView на вашем Fragment1 получите фрагменты из диспетчера фрагментов и воссоздайте свой ViewPager.

Я нашел этот ответ здесь, который почти такой же, но с немного более подробной информацией: https://stackoverflow.com/a/17135346/1417483

person Dreagen    schedule 29.04.2015
comment
Ваша структура выглядит довольно сложной, но я думаю, что если вы сохраните все фрагменты, которые вам нужно воссоздать, в методе onSaveInstanceState и получите их обратно в onCreate, он все равно должен работать. - person Dreagen; 29.04.2015
comment
Хорошо, я обновил свой вопрос, чтобы вы могли увидеть структуру моего приложения. Я должен поместить это в свою MainActivity, чтобы сохранить Fragment1 правильно? Но как это влияет на подфрагменты в ViewPager? Разве я не должен сохранять их состояния отдельно? Или их состояния сохраняются вместе с Fragment1? - person Nobbe; 29.04.2015
comment
Я никогда не делал этого на таком макете, как ваш, поэтому я уверен на 100%. Но я бы предположил, что ваши «внутренние» фрагменты будут храниться вместе с Fragment1, и пока вы сохраняете что-либо из каждого из этих внутренних фрагментов в своем собственном методе onSaveInstanceState, все это будет воссоздано, как вы и ожидали. - person Dreagen; 29.04.2015
comment
Я постараюсь быть немного яснее. Я думаю, вам нужно хранить только фрагмент 1 в вашей активности onSaveInstancestate, но, как я уже сказал. Поиграй с ним и посмотри - person Dreagen; 29.04.2015
comment
Хорошо, это имеет смысл, я попробую сегодня вечером, и, надеюсь, это сработает. Спасибо! - person Nobbe; 29.04.2015
comment
Я немного подумал об этом и обновил свой ответ тем, как, по моему мнению, это нужно сделать. Как я уже сказал, я сам этого не делал, но, надеюсь, это сработает, а если нет, то укажет вам правильное направление. - person Dreagen; 29.04.2015
comment
Хорошо, мне потребовалось некоторое время, чтобы попробовать это, но это работает. Спасибо еще раз за помощь! - person Nobbe; 07.05.2015
comment
Я думаю, что этот ответ на самом деле не помогает, так как ViewPager вместе с его PagerAdapter (FragmentStatePagerAdapter) должен и действительно обрабатывает добавление и удаление из fragmentManager. Вы не должны делать getFragmentManager().putFragment, так как это происходит внутри PagerAdapter. - person FrankKrumnow; 20.04.2016

FragmentPagerAdapter не вызывает onSaveInstanceState в фрагментах, которые больше не видны. Возможно, это и является причиной ваших проблем. Вместо этого попробуйте использовать FragmentStatePagerAdapter.

person EdgarK    schedule 23.05.2017
comment
Это помогло мне. Большое спасибо. - person Chris Palma; 18.08.2018

Наконец-то я получил решение и объяснение, почему это происходит. У меня была очень похожая проблема. Я понял, что когда я прокручивал прямо к моему 3-му подфрагменту, а затем обратно к 1-му, состояние 1-го сохранялось. Но не на изменение ориентации.

Я понял, что состояние сохраняется только в том случае, если вызывается destroyItem(..) адаптера. Это не вызывается автоматически при изменении ориентации.

Итак, теперь приSaveInstanceState MainFragment (который содержит ViewPager) я вызываю destroyItem для каждого активного фрагмента. Я проверяю Activity.isChangingConfigurations(), потому что onSaveInstanceState тоже вызывается, если я выключаю экран, но в этом случае все фрагменты просто остаются активными, и ничего не нужно менять.

Я расширил адаптер с помощью onDestroy (логическое сохранение), которое затем вызывается:

//in the main-fragment which holds the ViewPager:
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    if(getActivity()!=null && getActivity().isChangingConfigurations())
    {
        if (pager != null) {
        try {
            Log.w(TAG, TAG + " pager.onDestroy(true)");
            pager.onDestroy(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

И реализация в MyFragmentStatePagerAdapter:

public void onDestroy(boolean retain)
{
    this.m_allowDynamicLoading = false;
    if(retain)
    {
        try{
            if(getAdapter()!=null)
            {
                int limit = this.getOffscreenPageLimit();
                int currentIndex = this.getCurrentItem();
                if(currentIndex <0 || getAdapter().getCount() <= 0) 
                    return;
                //active fragments = fragments that are (less or equal) then
                //offscreenPageLimit awaw from the currently displayed one.
                for(int i = Math.min(currentIndex+limit, getAdapter().getCount()-1); 
                        i>= Math.max(0, currentIndex-limit);//erstes aktives fragment ist current - offscreen limit, aber nicht unter 0..
                        i--)    
                {
                    getAdapter().destroyItem(MessagingViewPager.this, i, getAdapter().instantiateItem(MessagingViewPager.this, i)); //this saved the state of that fragment, that will be restored after orientation change
                    Log.e(TAG,TAG + " orientation-change: destroying item " + i);                   
                }
            }
        }catch(Exception e){}   
    }
    else{ //retain = false is called onDestroy of the Fragment holding this Pager.
        try{
            this.setAdapter(null); 
            //this will destroy all fragments and forget the position
        }catch(Exception e){}   
    }       
}

Следует сказать и о других вещах:

  1. Адаптер принимает ChildFragmentManager, а не обычный
  2. Подфрагменты НЕ должны использовать setRetainInstance(true) (исключение в противном случае) MainFragment может (и в моем случае) использовать setRetainInstance(true)
  3. Создайте адаптер в onCreate MainFragment, чтобы он НЕ создавался заново при изменении ориентации. Настройка адаптера на пейджер должна выполняться в onCreateView.
  4. OnDestroy (или onDestroyView) MainFragment использует setAdapter(null) для завершения всех фрагментов и освобождения ресурсов. (в моем случае это делается с помощью MyViewPager.onDestroy(false))

и вуа: теперь вы получаете свой пакет saveInstanceState в SubFragments после изменения ориентации. И он не уничтожит предметы, если вы только выключите экран.

person FrankKrumnow    schedule 21.04.2016
comment
.... к сведению всех, кто использует это. Вы действительно боретесь с фреймворком, делая это. - person dell116; 18.11.2016