在ViewPager中替换Fragment

278
我正在尝试使用Fragment和ViewPager,使用FragmentPagerAdapter。我想要实现的是将位于ViewPager第一页上的一个片段替换为另一个片段。
这个ViewPager由两个页面组成。第一个页面是FirstPagerFragment,第二个页面是SecondPagerFragment。在第一个页面上点击按钮时,我想用NextFragment替换FirstPagerFragment。
以下是我的代码。
public class FragmentPagerActivity extends FragmentActivity {

    static final int NUM_ITEMS = 2;

    MyAdapter mAdapter;
    ViewPager mPager;

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

        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);

    }


    /**
     * Pager Adapter
     */
    public static class MyAdapter extends FragmentPagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

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

        @Override
        public Fragment getItem(int position) {

            if(position == 0) {
                return FirstPageFragment.newInstance();
            } else {
                return SecondPageFragment.newInstance();
            }

        }
    }


    /**
     * Second Page FRAGMENT
     */
    public static class SecondPageFragment extends Fragment {

        public static SecondPageFragment newInstance() {
            SecondPageFragment f = new SecondPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.second, container, false);

        }
    }

    /**
     * FIRST PAGE FRAGMENT
     */
    public static class FirstPageFragment extends Fragment {

        Button button;

        public static FirstPageFragment newInstance() {
            FirstPageFragment f = new FirstPageFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            View root = inflater.inflate(R.layout.first, container, false);
            button = (Button) root.findViewById(R.id.button);
            button.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    FragmentTransaction trans = getFragmentManager().beginTransaction();
                                    trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());
                    trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
                    trans.addToBackStack(null);
                    trans.commit();

                }

            });

            return root;
        }

        /**
     * Next Page FRAGMENT in the First Page
     */
    public static class NextFragment extends Fragment {

        public static NextFragment newInstance() {
            NextFragment f = new NextFragment();
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            //Log.d("DEBUG", "onCreateView");
            return inflater.inflate(R.layout.next, container, false);

        }
    }
}

......并且这里是xml文件

fragment_pager.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:padding="4dip"
        android:gravity="center_horizontal"
        android:layout_width="match_parent" android:layout_height="match_parent">

    <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1">
    </android.support.v4.view.ViewPager>

</LinearLayout>

first.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/first_fragment_root_id"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button android:id="@+id/button"
     android:layout_width="wrap_content" android:layout_height="wrap_content"
     android:text="to next"/>

</LinearLayout>

现在问题是...我应该使用哪个ID

trans.replace(R.id.first_fragment_root_id, NextFragment.newInstance());

如果我使用R.id.first_fragment_root_id,替换工作正常,但Hierarchy Viewer表现出奇怪的行为,如下所示。

开始时情况是

替换后情况变成了

可以看到有些问题,我期望在替换片段后找到与第一张图片中显示相同的状态。


你是通过某种方式生成了那个布局图还是手动完成的? - Jeroen Vannevel
@Noodles:我也需要做同样的事情。你的问题引发了一个很长的帖子,有许多不同的答案。最终哪个答案效果最好?非常感谢! - narb
1
@JeroenVannevel 你可能已经弄明白了,但那个布局图是 https://developer.android.com/tools/performance/hierarchy-viewer/index.html - Ankit Popli
遇到了一些问题,需要您的帮助。http://stackoverflow.com/questions/40149039/viewpager-recyclerview-issue - albert
18个回答

2
这是我实现的方法。
首先,在您想要实现按钮点击片段事件的 viewPager 选项卡中添加 Root_fragment 。例如:
@Override
public Fragment getItem(int position) {
  if(position==0)
      return RootTabFragment.newInstance();
  else
      return SecondPagerFragment.newInstance();
}

首先,RootTabFragment 应该包含 FragmentLayout 以实现片段更改。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/root_frame"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
</FrameLayout>

接下来,在RootTabFragmentonCreateView方法中,为你的FirstPagerFragment实现fragmentChange方法。

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, FirstPagerFragment.newInstance()).commit();

接下来,在FirstPagerFragment中实现你的按钮的onClick事件并再次改变片段。

getChildFragmentManager().beginTransaction().replace(R.id.root_frame, NextFragment.newInstance()).commit();

希望这能帮到你。

有点晚了,但仍然找不到简单的解决方案。在您的示例中,当您在NextFragment中旋转屏幕时,应用程序会切换回FirstPagerFragment... - Nikola Srdoč

2
这是我相对简单的解决方案。解决方案的关键是使用FragmentStatePagerAdapter而不是FragmentPagerAdapter,因为前者会自动删除未使用的片段,而后者仍然保留它们的实例。第二个关键是在getItem()中使用POSITION_NONE。我使用了一个简单的列表来跟踪我的片段。我的要求是一次性用新列表替换整个片段列表,但下面的内容可以很容易地修改以替换单个片段:
public class MyFragmentAdapter extends FragmentStatePagerAdapter {
    private List<Fragment> fragmentList = new ArrayList<Fragment>();
    private List<String> tabTitleList = new ArrayList<String>();

    public MyFragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    public void addFragments(List<Fragment> fragments, List<String> titles) {
        fragmentList.clear();
        tabTitleList.clear();
        fragmentList.addAll(fragments);
        tabTitleList.addAll(titles);
        notifyDataSetChanged();
    }

    @Override
    public int getItemPosition(Object object) {
        if (fragmentList.contains(object)) {
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int item) {
        if (item >= fragmentList.size()) {
            return null;
        }
        return fragmentList.get(item);
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return tabTitleList.get(position);
    }
}

2
在viewpager中替换片段非常复杂,但是非常可能,而且可以看起来非常流畅。首先,您需要让viewpager本身处理片段的删除和添加。当您替换SearchFragment内部的片段时,正在发生的是您的viewpager保留其片段视图。因此,当您尝试替换它时,SearchFragment被删除,导致页面变空白。
解决方案是在viewpager内部创建一个监听器,以处理在外部进行的更改,因此首先将以下代码添加到适配器底部。
public interface nextFragmentListener {
    public void fragment0Changed(String newFragmentIdentification);
}

接下来,您需要在您的 Viewpager 中创建一个私有类,用于监听您何时想要更改您的片段。例如,您可以添加类似以下内容的内容。请注意,它实现了刚刚创建的接口。因此,每当您调用此方法时,它将运行下面类中的代码。

private final class fragmentChangeListener implements nextFragmentListener {


    @Override
    public void fragment0Changed(String fragment) {
        //I will explain the purpose of fragment0 in a moment
        fragment0 = fragment;
        manager.beginTransaction().remove(fragAt0).commit();

        switch (fragment){
            case "searchFragment":
                fragAt0 = SearchFragment.newInstance(listener);
                break;
            case "searchResultFragment":
                fragAt0 = Fragment_Table.newInstance(listener);
                break;
        }

        notifyDataSetChanged();
    }

这里有两个主要的要点:

  1. fragAt0 is a "flexible" fragment. It can take on whatever fragment type you give it. This allows it to become your best friend in changing the fragment at position 0 to the fragment you desire.
  2. Notice the listeners that are placed in the 'newInstance(listener)constructor. These are how you will callfragment0Changed(String newFragmentIdentification)`. The following code shows how you create the listener inside of your fragment.

    static nextFragmentListener listenerSearch;

        public static Fragment_Journals newInstance(nextFragmentListener listener){
                listenerSearch = listener;
                return new Fragment_Journals();
            }
    

你可以在onPostExecute中调用变更。

private class SearchAsyncTask extends AsyncTask<Void, Void, Void>{

    protected Void doInBackground(Void... params){
        .
        .//some more operation
        .
    }
    protected void onPostExecute(Void param){

        listenerSearch.fragment0Changed("searchResultFragment");
    }

}

这将触发您的viewpager内部代码,将位置为0的fragAt0片段切换为新的searchResultFragment。在viewpager变得可用之前,您需要添加另外两个小组件。

其中一个是在viewpager的getItem覆盖方法中添加的。

@Override
public Fragment getItem(int index) {

    switch (index) {
    case 0:
        //this is where it will "remember" which fragment you have just selected. the key is to set a static String fragment at the top of your page that will hold the position that you had just selected.  

        if(fragAt0 == null){

            switch(fragment0){
            case "searchFragment":
                fragAt0 = FragmentSearch.newInstance(listener);
                break;
            case "searchResultsFragment":
                fragAt0 = FragmentSearchResults.newInstance(listener);
                break;
            }
        }
        return fragAt0;
    case 1:
        // Games fragment activity
        return new CreateFragment();

    }

如果没有这最后一步,你会得到一个空白页面。虽然有点无趣,但它是viewPager的必要组成部分。你必须重写viewpager的getItemPosition方法。通常情况下,该方法将返回POSITION_UNCHANGED,告诉viewpager保持一切不变,因此getItem永远不会被调用来将新的片段放置在页面上。以下是一个示例:

public int getItemPosition(Object object)
{
    //object is the current fragment displayed at position 0.  
    if(object instanceof SearchFragment && fragAt0 instanceof SearchResultFragment){
        return POSITION_NONE;
    //this condition is for when you press back
    }else if{(object instanceof SearchResultFragment && fragAt0 instanceof SearchFragment){
        return POSITION_NONE;
    }
        return POSITION_UNCHANGED
}

就像我所说的,代码变得非常复杂,但你基本上需要为你的情况创建一个自定义适配器。我提到的这些东西将使更改片段成为可能。吸收这一切可能需要很长时间,所以请耐心等待,但这一切都会有意义。花费时间完全是值得的,因为它可以让应用程序看起来非常流畅。

这里是处理返回按钮的要点。将其放置在MainActivity中。

 public void onBackPressed() {
    if(mViewPager.getCurrentItem() == 0) {
        if(pagerAdapter.getItem(0) instanceof FragmentSearchResults){
            ((Fragment_Table) pagerAdapter.getItem(0)).backPressed();
        }else if (pagerAdapter.getItem(0) instanceof FragmentSearch) {
            finish();
        }
    }

您需要在FragmentSearchResults中创建一个名为 backPressed() 的方法,该方法调用 fragment0changed。这与我之前展示的代码结合使用,将处理按下返回按钮的操作。祝你改变viewpager代码的好运。这需要很多工作,并且据我所知,没有快速的适应方法。就像我说的,您基本上正在创建自定义的viewpager适配器,并让它使用监听器来处理所有必要的更改。


谢谢您提供的解决方案。但是,您在旋转屏幕后是否遇到过任何问题?当我旋转屏幕并尝试重新加载同一选项卡中的另一个片段时,我的应用程序会崩溃。 - AnteGemini
我已经有一段时间没有做Android开发了,但我的猜测是你的应用程序依赖于某个数据,而这个数据现在已经不存在了。当你旋转屏幕时,整个活动会重新启动,如果你尝试在没有所有所需数据的情况下重新加载先前的状态(例如从onPause、onResume中的bundle),可能会导致崩溃。 - user3331142

2

我也做了一个解决方案,它与 Stacks 一起工作。这是一种更加 模块化 的方法,因此您不必在 FragmentPagerAdapter 中指定每个 Fragment 和 Detail Fragment。它是在 ActionbarSherlock 的示例基础上构建的,如果我没记错的话,这个示例是从 Google Demo 应用程序派生出来的。

/**
 * This is a helper class that implements the management of tabs and all
 * details of connecting a ViewPager with associated TabHost.  It relies on a
 * trick.  Normally a tab host has a simple API for supplying a View or
 * Intent that each tab will show.  This is not sufficient for switching
 * between pages.  So instead we make the content part of the tab host
 * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy
 * view to show as the tab content.  It listens to changes in tabs, and takes
 * care of switch to the correct paged in the ViewPager whenever the selected
 * tab changes.
 * 
 * Changed to support more Layers of fragments on each Tab.
 * by sebnapi (2012)
 * 
 */
public class TabsAdapter extends FragmentPagerAdapter
        implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener {
    private final Context mContext;
    private final TabHost mTabHost;
    private final ViewPager mViewPager;

    private ArrayList<String> mTabTags = new ArrayList<String>();
    private HashMap<String, Stack<TabInfo>> mTabStackMap = new HashMap<String, Stack<TabInfo>>();

    static final class TabInfo {
        public final String tag;
        public final Class<?> clss;
        public Bundle args;

        TabInfo(String _tag, Class<?> _class, Bundle _args) {
            tag = _tag;
            clss = _class;
            args = _args;
        }
    }

    static class DummyTabFactory implements TabHost.TabContentFactory {
        private final Context mContext;

        public DummyTabFactory(Context context) {
            mContext = context;
        }

        @Override
        public View createTabContent(String tag) {
            View v = new View(mContext);
            v.setMinimumWidth(0);
            v.setMinimumHeight(0);
            return v;
        }
    }

    public interface SaveStateBundle{
        public Bundle onRemoveFragment(Bundle outState);
    }

    public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) {
        super(activity.getSupportFragmentManager());
        mContext = activity;
        mTabHost = tabHost;
        mViewPager = pager;
        mTabHost.setOnTabChangedListener(this);
        mViewPager.setAdapter(this);
        mViewPager.setOnPageChangeListener(this);
    }

    /**
     * Add a Tab which will have Fragment Stack. Add Fragments on this Stack by using
     * addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args)
     * The Stack will hold always the default Fragment u add here.
     * 
     * DON'T ADD Tabs with same tag, it's not beeing checked and results in unexpected
     * beahvior.
     * 
     * @param tabSpec
     * @param clss
     * @param args
     */
    public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args){
        Stack<TabInfo> tabStack = new Stack<TabInfo>();

        tabSpec.setContent(new DummyTabFactory(mContext));
        mTabHost.addTab(tabSpec);
        String tag = tabSpec.getTag();
        TabInfo info = new TabInfo(tag, clss, args);

        mTabTags.add(tag);                  // to know the position of the tab tag 
        tabStack.add(info);
        mTabStackMap.put(tag, tabStack);
        notifyDataSetChanged();
    }

    /**
     * Will add the Fragment to Tab with the Tag _tag. Provide the Class of the Fragment
     * it will be instantiated by this object. Proivde _args for your Fragment.
     * 
     * @param fm
     * @param _tag
     * @param _class
     * @param _args
     */
    public void addFragment(FragmentManager fm, String _tag, Class<?> _class, Bundle _args){
        TabInfo info = new TabInfo(_tag, _class, _args);
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
        if(frag instanceof SaveStateBundle){
            Bundle b = new Bundle();
            ((SaveStateBundle) frag).onRemoveFragment(b);
            tabStack.peek().args = b;
        }
        tabStack.add(info);
        FragmentTransaction ft = fm.beginTransaction();
        ft.remove(frag).commit();
        notifyDataSetChanged();
    }

    /**
     * Will pop the Fragment added to the Tab with the Tag _tag
     * 
     * @param fm
     * @param _tag
     * @return
     */
    public boolean popFragment(FragmentManager fm, String _tag){
        Stack<TabInfo> tabStack = mTabStackMap.get(_tag);   
        if(tabStack.size()>1){
            tabStack.pop();
            Fragment frag = fm.findFragmentByTag("android:switcher:" + mViewPager.getId() + ":" + mTabTags.indexOf(_tag));
            FragmentTransaction ft = fm.beginTransaction();
            ft.remove(frag).commit();
            notifyDataSetChanged();
            return true;
        }
        return false;
    }

    public boolean back(FragmentManager fm) {
        int position = mViewPager.getCurrentItem();
        return popFragment(fm, mTabTags.get(position));
    }

    @Override
    public int getCount() {
        return mTabStackMap.size();
    }

    @Override
    public int getItemPosition(Object object) {
        ArrayList<Class<?>> positionNoneHack = new ArrayList<Class<?>>();
        for(Stack<TabInfo> tabStack: mTabStackMap.values()){
            positionNoneHack.add(tabStack.peek().clss);
        }   // if the object class lies on top of our stacks, we return default
        if(positionNoneHack.contains(object.getClass())){
            return POSITION_UNCHANGED;
        }
        return POSITION_NONE;
    }

    @Override
    public Fragment getItem(int position) {
        Stack<TabInfo> tabStack = mTabStackMap.get(mTabTags.get(position));
        TabInfo info = tabStack.peek();
        return Fragment.instantiate(mContext, info.clss.getName(), info.args);
    }

    @Override
    public void onTabChanged(String tabId) {
        int position = mTabHost.getCurrentTab();
        mViewPager.setCurrentItem(position);
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageSelected(int position) {
        // Unfortunately when TabHost changes the current tab, it kindly
        // also takes care of putting focus on it when not in touch mode.
        // The jerk.
        // This hack tries to prevent this from pulling focus out of our
        // ViewPager.
        TabWidget widget = mTabHost.getTabWidget();
        int oldFocusability = widget.getDescendantFocusability();
        widget.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
        mTabHost.setCurrentTab(position);
        widget.setDescendantFocusability(oldFocusability);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

}

在您的MainActivity中添加以下代码以实现“返回按钮”功能:
@Override
public void onBackPressed() {
  if (!mTabsAdapter.back(getSupportFragmentManager())) {
    super.onBackPressed();
  }
}

如果您希望在片段被移除时保存片段状态,请让您的片段实现接口SaveStateBundle,并在函数中返回一个包含您的保存状态的捆绑包。在实例化后通过this.getArguments()获取该捆绑包。

您可以像这样实例化选项卡:

mTabsAdapter.addTab(mTabHost.newTabSpec("firstTabTag").setIndicator("First Tab Title"),
                FirstFragmentActivity.FirstFragmentFragment.class, null);

如果您想在选项卡堆栈的顶部添加一个片段,则可以使用类似的方法。 重要提示:如果您想在两个选项卡的顶部拥有相同类的2个实例,则我认为它不起作用。 我很快就提出了这个解决方案,所以我只能分享它而不提供任何经验。


当我按下返回按钮时,我会收到一个空指针异常,并且我的替换片段也不可见。所以也许我做错了什么:更改片段的正确调用是什么? - Jeroen
更新:我已经解决了大部分问题,但是如果我在添加新片段后按下返回按钮,然后转到另一个选项卡并返回,我会得到一个空指针异常。所以我有两个问题:1:如何删除选项卡的历史记录,并确保只有最新的片段(堆栈顶部)仍然存在?2:如何摆脱空指针异常? - Jeroen
@Jeroen:是的,我也遇到了同样的问题,但我预料到了,如果您旋转设备,则会重新创建活动,这意味着您的旧变量被GB吃掉了,解决方案是在onDestroy方法中将堆栈和tabinfos保存到savestatebundle中,然后在重新创建活动时恢复tabsadapter状态。有时间会做,但目前还有其他事情要做。还有一个快速脏解决旋转设备的方法,谷歌一下与清单文件有关。 - seb
1
@seb 谢谢。我之前在使用这个代码,但现在嵌套片段已经可行了,所以不再是问题。 :) - Garcon
@Jeroen 看起来已经解决了。如果您有其他问题,请随时问。 - seb
显示剩余5条评论

1

我正在做类似于Wize的事情,但在我的答案中,您可以随时在两个片段之间切换。而且,在更改屏幕方向等方面,我遇到了一些问题。以下是PagerAdapter的外观:

    public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;
     private Map<Integer, String> mFragmentTags;
     private boolean isNextFragment=false;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
         mFragmentTags = new HashMap<Integer, String>();
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {


            if (isPager) {
                mFragmentAtPos0 = new FirstPageFragment();
            } else {
                mFragmentAtPos0 = new NextFragment();
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

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


 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }


    public void onChange(boolean isNextFragment) {

        if (mFragmentAtPos0 == null)
            mFragmentAtPos0 = getFragment(0);
        if (mFragmentAtPos0 != null)
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();


        if (!isNextFragment) {
            mFragmentAtFlashcards = new FirstPageFragment();
        } else {
            mFragmentAtFlashcards = new NextFragment();
        }

        notifyDataSetChanged();


    }


    @Override
    public int getItemPosition(Object object)
    {
        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)
            return POSITION_NONE;
         if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstPageFragment)
            return POSITION_NONE;
        return POSITION_UNCHANGED;
    }


    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}

我在适配器容器活动中实现了监听器,以便在将其附加到片段时将其放置到片段中,这是该活动:

    public class PagerContainerActivity extends AppCompatActivity implements ChangeFragmentListener {

//...

  @Override
    public void onChange(boolean isNextFragment) {
        if (pagerAdapter != null)
            pagerAdapter.onChange(isNextFragment);


    }

//...
}

然后在片段中附加侦听器并调用它:

public class FirstPageFragment extends Fragment{


private ChangeFragmentListener changeFragmentListener;


//...
 @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        changeFragmentListener = ((PagerContainerActivity) activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
        changeFragmentListener = null;
    }
//...
//in the on click to change the fragment
changeFragmentListener.onChange(true);
//...
}

最后是监听器:
public interface changeFragmentListener {

    void onChange(boolean isNextFragment);

}

1
我按照@wize和@mdelolmo的回答得到了解决方案。非常感谢。但是,我对这些解决方案进行了一些调整以改善内存消耗。
我观察到的问题是:
他们保存了被替换的Fragment实例。在我的情况下,它是一个包含MapView的Fragment,我认为它很昂贵。因此,我维护了FragmentPagerPositionChanged(POSITION_NONE或POSITION_UNCHANGED)而不是Fragment本身。
以下是我的实现。
  public static class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {

    private SwitchFragListener mSwitchFragListener;
    private Switch mToggle;
    private int pagerAdapterPosChanged = POSITION_UNCHANGED;
    private static final int TOGGLE_ENABLE_POS = 2;


    public DemoCollectionPagerAdapter(FragmentManager fm, Switch toggle) {
        super(fm);
        mToggle = toggle;

        mSwitchFragListener = new SwitchFragListener();
        mToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mSwitchFragListener.onSwitchToNextFragment();
            }
        });
    }

    @Override
    public Fragment getItem(int i) {
        switch (i)
        {
            case TOGGLE_ENABLE_POS:
                if(mToggle.isChecked())
                {
                    return TabReplaceFragment.getInstance();
                }else
                {
                    return DemoTab2Fragment.getInstance(i);
                }

            default:
                return DemoTabFragment.getInstance(i);
        }
    }

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

    @Override
    public CharSequence getPageTitle(int position) {
        return "Tab " + (position + 1);
    }

    @Override
    public int getItemPosition(Object object) {

        //  This check make sures getItem() is called only for the required Fragment
        if (object instanceof TabReplaceFragment
                ||  object instanceof DemoTab2Fragment)
            return pagerAdapterPosChanged;

        return POSITION_UNCHANGED;
    }

    /**
     * Switch fragments Interface implementation
     */
    private final class SwitchFragListener implements
            SwitchFragInterface {

        SwitchFragListener() {}

        public void onSwitchToNextFragment() {

            pagerAdapterPosChanged = POSITION_NONE;
            notifyDataSetChanged();
        }
    }

    /**
     * Interface to switch frags
     */
    private interface SwitchFragInterface{
        void onSwitchToNextFragment();
    }
}

演示链接在这里.. https://youtu.be/l_62uhKkLyM

为了演示目的,我在第二个位置使用了2个片段TabReplaceFragmentDemoTab2Fragment。在所有其他情况下,我都使用DemoTabFragment实例。

解释:

我将Switch从Activity传递到DemoCollectionPagerAdapter。根据此开关的状态,我们将显示正确的片段。当更改开关检查时,我调用SwitchFragListeneronSwitchToNextFragment方法,在那里我将pagerAdapterPosChanged变量的值更改为POSITION_NONE。了解更多关于POSITION_NONE的信息。这将使getItem失效,我有逻辑来实例化正确的片段。如果解释有点混乱,请见谅。

再次感谢@wize和@mdelolmo提出的原始想法。

希望这对你有所帮助。 :)

如果这个实现有任何缺陷,请告诉我。这将对我的项目非常有帮助。


1
我发现了一个简单的解决方案,即使你想在中间添加新的片段或替换当前片段,它也能正常工作。在我的解决方案中,你需要重写getItemId()方法,该方法应返回每个片段的唯一ID,而不是默认的位置。以下是代码示例:
public class DynamicPagerAdapter extends FragmentPagerAdapter {

private ArrayList<Page> mPages = new ArrayList<Page>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();

public DynamicPagerAdapter(FragmentManager fm) {
    super(fm);
}

public void replacePage(int position, Page page) {
    mPages.set(position, page);
    notifyDataSetChanged();
}

public void setPages(ArrayList<Page> pages) {
    mPages = pages;
    notifyDataSetChanged();
}

@Override
public Fragment getItem(int position) {
    if (mPages.get(position).mPageType == PageType.FIRST) {
        return FirstFragment.newInstance(mPages.get(position));
    } else {
        return SecondFragment.newInstance(mPages.get(position));
    }
}

@Override
public int getCount() {
    return mPages.size();
}

@Override
public long getItemId(int position) {
    // return unique id
    return mPages.get(position).getId();
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Fragment fragment = (Fragment) super.instantiateItem(container, position);
    while (mFragments.size() <= position) {
        mFragments.add(null);
    }
    mFragments.set(position, fragment);
    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    mFragments.set(position, null);
}

@Override
public int getItemPosition(Object object) {
    PagerFragment pagerFragment = (PagerFragment) object;
    Page page = pagerFragment.getPage();
    int position = mFragments.indexOf(pagerFragment);
    if (page.equals(mPages.get(position))) {
        return POSITION_UNCHANGED;
    } else {
        return POSITION_NONE;
    }
}
}

注意:在这个例子中,FirstFragmentSecondFragment都扩展了抽象类 PageFragment,该类具有方法 getPage()


0

经过研究,我发现了一个简短的代码解决方案。 首先,在片段上创建一个公共实例,并在 onSaveInstanceState 中删除您的片段,如果片段在方向更改时不会重新创建。

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}

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