61

问题

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

情况

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

行为

只要不按下后退按钮,一切正常。一旦用户关闭其中一个子 PageLists,就会重新创建先前的 List,但没有先前显示的 Page。浏览父 PageList 上的其他页面仍然有效。

代码

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

活动

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

}

页面列表片段

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

页面片段

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

}
4

4 回答 4

160

经过长时间的调查,结果证明是片段管理器有问题。

当使用类似于上面的片段事务将片段重新附加到页面列表的构造时,将被静默丢弃。基本上是相同的问题导致

java.lang.IllegalStateException: Recursive entry to executePendingTransactions 

尝试更改 FragmentPager 中的片段时。

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

代替

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

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

参见:github

于 2013-05-22T03:27:13.347 回答
10

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

于 2015-07-09T08:19:44.440 回答
2

在我的情况下,层次结构是:

MainActivity-> MainFragment-> TabLayout+ ViewPager-> AccountsFragment+ SavingsFragment+InvestmentsFragment等等。

我遇到的问题是我无法使用childFragmentManager,因为单击该项目Account view(位于Fragments 之一内ViewPager)需要替换MainFragment即整个屏幕。

在此处输入图像描述

使用MainFragments 主机,Fragment即通过getFragmentManager()启用替换,但是当弹出后台堆栈时,我最终得到了这个屏幕:

在此处输入图像描述

通过查看ViewPager为空的布局检查器,这一点也很明显。

在此处输入图像描述

显然,查看 restoreFragment的 s 您会注意到它们View已恢复,但与弹出状态的层次结构不匹配。为了产生最小的影响而不是强制重新创建Fragments,我重新编写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的 s 重新附加到ViewPager.

于 2018-10-05T08:34:14.347 回答
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)
    }
}
于 2018-10-23T13:34:29.203 回答