ViewPager中的Fragment在popBackStack后没有恢复

63

问题

从另一个片段返回后,片段不会重新附加到其托管 ViewPager 上。

情况

在一个 Activity 中托管一个 Fragment,该 Fragment 的布局包含一个 ViewPager(在下面的示例中为 PageListFragment)。ViewPager 由 FragmentStateViewPagerAdapter 填充。位于 pager 内部的单个片段(在下面的示例中为 PageFragment)可以打开子页面列表,其中包含一组新页面。

行为

只要没有按下返回按钮,所有工作都正常。当用户关闭其中一个子 PageLists 时,前一个 List 将被重新创建,但不包括以前显示的 Page。在父 PageList 上滑动其他页面仍然有效。

代码

可以在 github 上找到示例应用程序:

Activity

public class MainActivity extends FragmentActivity {

private static final String CURRENT_FRAGMENT = MainActivity.class.getCanonicalName() + ".CURRENT_FRAGMENT";

public static final String ARG_PARENTS = "Parents";

public void goInto(String mHostingLevel, String mPosition) {
    Fragment hostingFragment = newHostingFragment(mHostingLevel, mPosition);
    addFragment(hostingFragment);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    addBaseFragment();
}

private void addBaseFragment() {
    Fragment hostingFragment = newHostingFragment("", "");
    addFragment(hostingFragment);
}

private Fragment newHostingFragment(String mHostingLevel, String oldPosition) {
    Fragment hostingFragment = new PageListFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARENTS, mHostingLevel + oldPosition +" > ");
    hostingFragment.setArguments(args);
    return hostingFragment;
}

private void addFragment(Fragment hostingFragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.fragmentSpace, hostingFragment, CURRENT_FRAGMENT);
    transaction.addToBackStack(null);
    transaction.commit();
}

}

PageListFragment

public class PageListFragment extends Fragment {

private String mParentString;

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

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_hosting, container, false);
}

@Override
public void onResume() {
    mParentString = getArguments().getString(MainActivity.ARG_PARENTS);
    ViewPager viewPager = (ViewPager) getView().findViewById(R.id.viewPager);
    viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));
    super.onResume();
}

private static class SimpleFragmentStatePagerAdapter extends FragmentStatePagerAdapter {

    private String mHostingLevel;

    public SimpleFragmentStatePagerAdapter(FragmentManager fm, String hostingLevel) {
        super(fm);
        this.mHostingLevel = hostingLevel;
    }

    @Override
    public android.support.v4.app.Fragment getItem(int position) {
        PageFragment pageFragment = new PageFragment();
        Bundle args = new Bundle();
        args.putString(MainActivity.ARG_PARENTS, mHostingLevel);
        args.putInt(PageFragment.ARG_POSITION, position);
        pageFragment.setArguments(args);
        return pageFragment;
    }

    @Override
    public int getCount() {
        return 5;
    }
}
}

PageFragment

public class PageFragment extends Fragment {

public static final String ARG_POSITION = "Position";

private String mHostingLevel;
private int mPosition;

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View contentView = inflater.inflate(R.layout.fragment_page, container, false);
    setupTextView(contentView);
    setupButton(contentView);
    return contentView;
}

private void setupTextView(View contentView) {
    mPosition = getArguments().getInt(ARG_POSITION);
    mHostingLevel = getArguments().getString(MainActivity.ARG_PARENTS);
    TextView text = (TextView) contentView.findViewById(R.id.textView);
    text.setText("Parent Fragments " + mHostingLevel + " \n\nCurrent Fragment "+ mPosition);
}

private void setupButton(View contentView) {
    Button button = (Button) contentView.findViewById(R.id.button);
    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            openNewLevel();
        }
    });
}

protected void openNewLevel() {
    MainActivity activity = (MainActivity) getActivity();
    activity.goInto(mHostingLevel, Integer.toString(mPosition));
}

}

只是提一下:简单地覆盖 public int getItemPosition(Object object) 是行不通的。 - Paul
4个回答

161

经过漫长的调查,问题被发现是由于片段管理器引起的。

当使用上述结构时,片段事务重新将片段附加到页面列表中时会被静默丢弃。这基本上是导致一个问题的根源,就像导致

java.lang.IllegalStateException: Recursive entry to executePendingTransactions 

尝试修改FragmentPager内部片段时。

对于此错误问题的解决方案也适用于此处。在构建FragmentStatePagerAdapter时,请提供正确的子片段管理器。

不要

    viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getFragmentManager(),mParentString));

    viewPager.setAdapter(new SimpleFragmentStatePagerAdapter(getChildFragmentManager(),mParentString));

另请参阅:github


谢谢,这个(有点)起作用了。但现在onSaveInstanceState在各个片段(在你的情况下是PageFragment)中没有被调用。 - ivanfeli

10

Paul没有提到的是,如果您使用getChildFragmentManager,那么您将遇到“返回时屏幕空白”的问题。


1
有任何解决办法吗?在SO上有很多关于这个问题的问题,它们都建议我们使用子片段管理器来解决,我也这样做了。但是我的 'FragmentStatePagerAdapter' 在返回时不会重新附加其片段,导致出现空白屏幕。 - SqueezyMo
哈哈!+1,找到了! - Shivansh
那么空白画面的解决方案是什么?我在这里问了同样的问题:http://stackoverflow.com/questions/42483105/white-screen-in-view-pager-fragment-state-pager-adapter-when-we-come-back - Mahesh
@Mahesh,我已经回复了你的帖子。希望能对某些人有所帮助!帖子链接 - Rahul Khurana

2
我的情况下的层级关系是: MainActivity -> MainFragment -> TabLayout+ViewPager -> AccountsFragment+SavingsFragment+InvestmentsFragment 等等。
我的问题是,由于需要在一个 ViewPagerFragment 中点击 Account view(该项位于 Fragment 内部),需要替换整个屏幕而无法使用 childFragmentManager

enter image description here

使用MainFragment的宿主Fragment,即传递getFragmentManager()启用了替换功能。但是,当弹出返回堆栈时,我最终得到了这个屏幕:

enter image description here

通过查看布局检查器,也可以发现 ViewPager 是空的。

enter image description here

显然,查看恢复的Fragment时,您会注意到它们的View已经恢复了,但不会与弹出状态的层次结构匹配。为了最小限度地影响和不强制重新创建Fragment,我重写了FragmentStatePagerAdapter,并进行了以下更改:
我复制了整个FragmentStatePagerAdapter的代码,并进行了更改。
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    // If we already have this item instantiated, there is nothing
    // to do.  This can happen when we are restoring the entire pager
    // from its saved state, where the fragment manager has already
    // taken care of restoring the fragments we previously had instantiated.
    if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);
        if (f != null) {
            return f;
        }
    }
...
}

使用

@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
    // If we already have this item instantiated, there is nothing
    // to do.  This can happen when we are restoring the entire pager
    // from its saved state, where the fragment manager has already
    // taken care of restoring the fragments we previously had instantiated.
    if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);
        if (f != null) {
            if (mCurTransaction == null) {
                mCurTransaction = mFragmentManager.beginTransaction();
            }

            mCurTransaction.detach(f);
            mCurTransaction.attach(f);

            return f;
        }
    }
...
}

这样做可以有效地确保恢复的 Fragment 被重新附加到 ViewPager 上。

0

删除所有页面片段,使它们可以稍后重新添加

当您返回到viewpager屏幕时,由于FragmentStatePagerAdapter未重新连接它们,因此页面片段未被附加。作为解决方法,在调用popbackstack()后删除viewpager中的所有片段,这将允许它们由您的初始代码重新添加。

【此示例已使用Kotlin编写】

//Clear all fragments from the adapter before they are re-added.
for (i: Int in 0 until adapter.count) {
    val item = childFragmentManager.findFragmentByTag("f$i")
    if (item != null) {
        adapter.destroyItem(container!!, i, item)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接