简体   繁体   English

viewpager savedinstancestate中的片段始终为null

[英]Fragment in viewpager savedinstancestate is always null

I know this question has been asked before but none of the answers given so far is of any help to me. 我知道之前已经问过这个问题,但到目前为止给出的答案都没有对我有任何帮助。

I have a viewpager which is populated with fragments (android.support.v4.app.Fragment) from a FragmentStatePagerAdapter . 我有一个viewpager,它填充了FragmentStatePagerAdapter中的片段(android.support.v4.app.Fragment)。 Some of these fragments contain logic that needs to be retained when the orientation changes, such as keeping track of which view is currently selected. 其中一些片段包含在方向改变时需要保留的逻辑,例如跟踪当前选择的视图。

However, although I save the data in question in onSaveInstanceState the savedInstanceState is always null. 但是,尽管我在onSaveInstanceState中保存了有问题的数据,但savedInstanceState始终为null。 I can solve this by storing the data in a static variable (which since I only have one instance of each fragment would work for me) but i found this to be a quite ugly solution and there has to be a proper way of doing this. 我可以通过将数据存储在静态变量中来解决这个问题(因为我只有每个片段的一个实例对我有用)但我发现这是一个非常难看的解决方案,并且必须有一个正确的方法来做到这一点。

This is one of the fragments that doesn't retain it's state on rotation: 这是在旋转时不保留其状态的片段之一:

    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));
    }


}

How do I solve this without having to save my states in static variables? 如何在不必将状态保存在静态变量中的情况下解决这个问题?

Edit 编辑

I should point out that all of the fragments are programatically created and as such they do not have an id and I read that that might be the problem but I don't know how to solve that either. 我应该指出所有的片段都是以编程方式创建的,因此它们没有id,我读到这可能是问题,但我不知道如何解决这个问题。

Also my application is structured like this: 我的应用程序结构如下:

  • MainActivity with NavigationDrawer 使用NavigationDrawer的MainActivity
    • Fragment1 片段1
      • ViewPager ViewPager
        • subfragment1 - subfragment5 subfragment1 - subfragment5
    • Fragment2 Fragment2
    • Fragment3 Fragment3

The fragments whose states I'm having trouble with are the subfragments. 我遇到麻烦的状态的片段是子片段。

In your Activity which is hosting your Fragment you need to store a refernce to the fragment in the Bundle . 在托管FragmentActivity ,您需要存储对Bundle片段的Bundle

Something like this should work for you 这样的事情对你有用

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 is the instance of the Fragment1 that you mentioned in your question that needs to get recreated. fragment1是您在问题中提到的需要重新创建的Fragment1的实例。

I haven't done this with a structure like yours before but this is how I would start: 我之前没有使用像你这样的结构,但这就是我要开始的方式:

In the onSaveInstanceState in your Fragment1 I believe you would need to do the same with each of the fragments in your ViewPager . Fragment1onSaveInstanceState中,我相信您需要对ViewPager每个片段执行相同的ViewPager Then in the onCreateView on your Fragment1 get the fragments from the fragment manager and recreate your ViewPager . 然后在Fragment1上的onCreateView从片段管理器中获取片段并重新创建ViewPager

I have found this answer here which is pretty much the same but has a little more detail: https://stackoverflow.com/a/17135346/1417483 我在这里找到了这个答案几乎相同,但有更多细节: https//stackoverflow.com/a/17135346/1417483

FragmentPagerAdapter is not calling onSaveInstanceState in frgments that are not visible anymore. FragmentPagerAdapter不再在不再可见的frgments中调用onSaveInstanceState。 Maybe this is what causing your issues. 也许这就是导致你的问题的原因。 Try to use FragmentStatePagerAdapter instead. 尝试使用FragmentStatePagerAdapter。

I finally got a solution and explanation why this is happening. 我终于找到了解决方案并解释了为什么会这样。 I had a very similar problem. 我有一个非常相似的问题。 I recognized that when I was scrolling right to my 3rd subfragment and then back to the 1st then the state of the 1st got saved. 我认识到,当我向右滚动到第3个子碎片然后回到第1个时,第1个状态就被保存了。 But not on Orientation Change. 但不是取向取向。

I figured that the state is only saved if the adapter's destroyItem(..) is called. 我想只有在调用适配器的destroyItem(..)时才会保存状态。 That is not called automatically if orientation changes. 如果方向改变,则不会自动调用。

So now onSaveInstanceState of the MainFragment (which holds the ViewPager) I call destroyItem for each active fragment. 所以现在在MainFragment的onSaveInstanceState(它拥有ViewPager)我为每个活动片段调用destroyItem。 I check for activity.isChangingConfigurations() because onSaveInstanceState is called too if I turn off the screen, but in that case all the fragments just stay active and nothing has to be changed. 我检查activity.isChangingConfigurations(),因为如果我关闭屏幕也会调用onSaveInstanceState,但在这种情况下,所有片段都保持活动状态,不需要更改任何内容。

I extended the adapter with an onDestroy(boolean retain) which is called then: 我使用onDestroy(boolean retain)扩展了适配器,然后调用它:

//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();
        }
    }

And the implementation in MyFragmentStatePagerAdapter: 并在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){}   
    }       
}

Some other things are to be said: 其他一些事情要说:

  1. Adapter takes the ChildFragmentManager not the normal one Adapter使ChildFragmentManager不正常
  2. The SubFragments must NOT use setRetainInstance(true) (Exception otherwise) The MainFragment can (and in my case does) use setRetainInstance(true) SubFragments不能使用setRetainInstance(true)(否则为异常)MainFragment可以(在我的例子中)使用setRetainInstance(true)
  3. Create the adapter in onCreate of the MainFragment, so it will NOT be recreated on Orientation change. 在MainFragment的onCreate中创建适配器,因此不会在Orientation更改时重新创建它。 Setting adapter to pager should be done in onCreateView. 将适配器设置为寻呼机应在onCreateView中完成。
  4. OnDestroy (or onDestroyView) of the MainFragment use setAdapter(null) to terminate all fragments and release resources. MainFragment的OnDestroy(或onDestroyView)使用setAdapter(null)来终止所有片段并释放资源。 (This is done by MyViewPager.onDestroy(false) in my case) (在我的例子中,这是由MyViewPager.onDestroy(false)完成的)

et voiá: now you get your savedInstanceState bundle in the SubFragments after the orientation change. etvoiá:现在,在方向更改后,您将在SubFragments中获取savedInstanceState包。 And it will not destroy the items if you only switch the screen off. 如果只关闭屏幕,它不会破坏物品。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM