viewpager 中的片段 savedinstancestate 始终为 null
Fragment in viewpager savedinstancestate is always null
我知道以前有人问过这个问题,但到目前为止 none 的答案对我有帮助。
我有一个 viewpager,其中填充了来自 FragmentStatePagerAdapter 的片段 (android.support.v4.app.Fragment)。其中一些片段包含方向更改时需要保留的逻辑,例如跟踪当前选择的视图。
然而,尽管我将相关数据保存在 onSaveInstanceState 中,但 savedInstanceState 始终为空。我可以通过将数据存储在一个静态变量中来解决这个问题(因为每个片段只有一个实例对我有用),但我发现这是一个非常丑陋的解决方案,必须有一种正确的方法来做到这一点。
这是在旋转时不保留其状态的片段之一:
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));
}
}
如何解决这个问题而不必将我的状态保存在静态变量中?
编辑
我应该指出,所有片段都是以编程方式创建的,因此它们没有 ID,我读到这可能是问题所在,但我也不知道如何解决。
我的申请也是这样构成的:
- 带有 NavigationDrawer 的 MainActivity
- 片段1
- ViewPager
- 子片段 1 - 子片段 5
- 片段2
- 片段 3
我遇到问题的片段是子片段。
在托管您的 Fragment
的 Activity
中,您需要在 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 实例。
我以前没有用像你这样的结构来做这件事,但我会这样开始:
在您的 Fragment1
中的 onSaveInstanceState
我相信您需要对 ViewPager
中的每个片段执行相同的操作。然后在 Fragment1
上的 onCreateView
中从片段管理器获取片段并重新创建 ViewPager
.
我在这里找到了这个几乎相同但有更多细节的答案:
我终于找到了解决方案并解释了为什么会发生这种情况。我有一个非常相似的问题。我意识到,当我向右滚动到我的第三个子片段然后回到第一个时,第一个的状态被保存了。但不是方向变化。
我认为只有在调用适配器的 destroyItem(..) 时才会保存状态。如果方向改变,则不会自动调用。
现在,我为每个活动片段调用了 MainFragment(包含 ViewPager)的 onSaveInstanceState。我检查了 activity.isChangingConfigurations(),因为如果我关闭屏幕,也会调用 onSaveInstanceState,但在那种情况下,所有片段都保持活动状态,无需更改任何内容。
我用一个 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();
}
}
以及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){}
}
}
还有一些话要说:
- 适配器采用 ChildFragmentManager 而不是普通的
- 子片段不得使用 setRetainInstance(true)(否则例外)
MainFragment 可以(在我的例子中)使用 setRetainInstance(true)
- 在 MainFragment 的 onCreate 中创建适配器,因此它不会在 Orientation 更改时重新创建。将适配器设置为寻呼机应该在 onCreateView 中完成。
- MainFragment的OnDestroy(或onDestroyView)使用setAdapter(null)终止所有片段并释放资源。 (在我的例子中,这是由 MyViewPager.onDestroy(false) 完成的)
瞧瞧:现在您在方向更改后在 SubFragments 中获得了 savedInstanceState 包。而且只要关掉屏幕也不会破坏物品
FragmentPagerAdapter 不会在不再可见的片段中调用 onSaveInstanceState。也许这就是导致您出现问题的原因。
尝试改用 FragmentStatePagerAdapter。
我知道以前有人问过这个问题,但到目前为止 none 的答案对我有帮助。
我有一个 viewpager,其中填充了来自 FragmentStatePagerAdapter 的片段 (android.support.v4.app.Fragment)。其中一些片段包含方向更改时需要保留的逻辑,例如跟踪当前选择的视图。
然而,尽管我将相关数据保存在 onSaveInstanceState 中,但 savedInstanceState 始终为空。我可以通过将数据存储在一个静态变量中来解决这个问题(因为每个片段只有一个实例对我有用),但我发现这是一个非常丑陋的解决方案,必须有一种正确的方法来做到这一点。
这是在旋转时不保留其状态的片段之一:
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));
}
}
如何解决这个问题而不必将我的状态保存在静态变量中?
编辑
我应该指出,所有片段都是以编程方式创建的,因此它们没有 ID,我读到这可能是问题所在,但我也不知道如何解决。
我的申请也是这样构成的:
- 带有 NavigationDrawer 的 MainActivity
- 片段1
- ViewPager
- 子片段 1 - 子片段 5
- ViewPager
- 片段2
- 片段 3
- 片段1
我遇到问题的片段是子片段。
在托管您的 Fragment
的 Activity
中,您需要在 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 实例。
我以前没有用像你这样的结构来做这件事,但我会这样开始:
在您的 Fragment1
中的 onSaveInstanceState
我相信您需要对 ViewPager
中的每个片段执行相同的操作。然后在 Fragment1
上的 onCreateView
中从片段管理器获取片段并重新创建 ViewPager
.
我在这里找到了这个几乎相同但有更多细节的答案:
我终于找到了解决方案并解释了为什么会发生这种情况。我有一个非常相似的问题。我意识到,当我向右滚动到我的第三个子片段然后回到第一个时,第一个的状态被保存了。但不是方向变化。
我认为只有在调用适配器的 destroyItem(..) 时才会保存状态。如果方向改变,则不会自动调用。
现在,我为每个活动片段调用了 MainFragment(包含 ViewPager)的 onSaveInstanceState。我检查了 activity.isChangingConfigurations(),因为如果我关闭屏幕,也会调用 onSaveInstanceState,但在那种情况下,所有片段都保持活动状态,无需更改任何内容。
我用一个 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();
}
}
以及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){}
}
}
还有一些话要说:
- 适配器采用 ChildFragmentManager 而不是普通的
- 子片段不得使用 setRetainInstance(true)(否则例外) MainFragment 可以(在我的例子中)使用 setRetainInstance(true)
- 在 MainFragment 的 onCreate 中创建适配器,因此它不会在 Orientation 更改时重新创建。将适配器设置为寻呼机应该在 onCreateView 中完成。
- MainFragment的OnDestroy(或onDestroyView)使用setAdapter(null)终止所有片段并释放资源。 (在我的例子中,这是由 MyViewPager.onDestroy(false) 完成的)
瞧瞧:现在您在方向更改后在 SubFragments 中获得了 savedInstanceState 包。而且只要关掉屏幕也不会破坏物品
FragmentPagerAdapter 不会在不再可见的片段中调用 onSaveInstanceState。也许这就是导致您出现问题的原因。 尝试改用 FragmentStatePagerAdapter。