我试图使用片段与一个ViewPager使用FragmentPagerAdapter。 我想要实现的是替换一个片段,位于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显示一个奇怪的行为,如下所示。

一开始的情况是

更换后的情况是

正如你所看到的,有一些错误,我希望在替换片段后找到与第一张图片中相同的状态。


当前回答

基于@wize的回答,我发现它很有用,也很优雅,我可以部分实现我想要的,因为我想要电缆在被替换后回到第一个片段。我稍微修改了一下他的代码就实现了。

这将是FragmentPagerAdapter:

public static class MyAdapter extends FragmentPagerAdapter {
    private final class CalendarPageListener implements
            CalendarPageFragmentListener {
        public void onSwitchToNextFragment() {
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                    .commit();
            if (mFragmentAtPos0 instanceof FirstFragment){
                mFragmentAtPos0 = NextFragment.newInstance(listener);
            }else{ // Instance of NextFragment
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            notifyDataSetChanged();
        }
    }

    CalendarPageListener listener = new CalendarPageListener();;
    private Fragment mFragmentAtPos0;
    private FragmentManager mFragmentManager;

    public MyAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }

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

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

    @Override
    public Fragment getItem(int position) {
        if (position == 0)
            return Portada.newInstance();
        if (position == 1) { // Position where you want to replace fragments
            if (mFragmentAtPos0 == null) {
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            return mFragmentAtPos0;
        }
        if (position == 2)
            return Clasificacion.newInstance();
        if (position == 3)
            return Informacion.newInstance();

        return null;
    }
}

public interface CalendarPageFragmentListener {
    void onSwitchToNextFragment();
}

要执行替换,只需定义一个静态字段,类型为CalendarPageFragmentListener,并通过相应片段的newInstance方法初始化,并分别调用firstfragment . pagelister . onswitchtonextfragment()或nextfragment . pagelister . onswitchtonextfragment()。

其他回答

在你的onCreateView方法中,容器实际上是一个ViewPager实例。

所以,只是打电话

ViewPager vpViewPager = (ViewPager) container;
vpViewPager.setCurrentItem(1);

将改变当前片段在你的ViewPager。

基于@wize的回答,我发现它很有用,也很优雅,我可以部分实现我想要的,因为我想要电缆在被替换后回到第一个片段。我稍微修改了一下他的代码就实现了。

这将是FragmentPagerAdapter:

public static class MyAdapter extends FragmentPagerAdapter {
    private final class CalendarPageListener implements
            CalendarPageFragmentListener {
        public void onSwitchToNextFragment() {
            mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                    .commit();
            if (mFragmentAtPos0 instanceof FirstFragment){
                mFragmentAtPos0 = NextFragment.newInstance(listener);
            }else{ // Instance of NextFragment
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            notifyDataSetChanged();
        }
    }

    CalendarPageListener listener = new CalendarPageListener();;
    private Fragment mFragmentAtPos0;
    private FragmentManager mFragmentManager;

    public MyAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
    }

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

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

    @Override
    public Fragment getItem(int position) {
        if (position == 0)
            return Portada.newInstance();
        if (position == 1) { // Position where you want to replace fragments
            if (mFragmentAtPos0 == null) {
                mFragmentAtPos0 = FirstFragment.newInstance(listener);
            }
            return mFragmentAtPos0;
        }
        if (position == 2)
            return Clasificacion.newInstance();
        if (position == 3)
            return Informacion.newInstance();

        return null;
    }
}

public interface CalendarPageFragmentListener {
    void onSwitchToNextFragment();
}

要执行替换,只需定义一个静态字段,类型为CalendarPageFragmentListener,并通过相应片段的newInstance方法初始化,并分别调用firstfragment . pagelister . onswitchtonextfragment()或nextfragment . pagelister . onswitchtonextfragment()。

替换viewpager中的片段是相当复杂的,但这是非常可能的,可以看起来超级光滑。首先,您需要让viewpager本身处理片段的删除和添加。发生的事情是,当你替换SearchFragment内部的片段时,你的viewpager保留它的片段视图。因此,您最终会得到一个空白页,因为当您试图替换它时,SearchFragment会被删除。

解决方案是在viewpager内部创建一个侦听器,它将处理在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();
    }

这里主要有两点需要指出:

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. 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内部的代码来切换位置为零位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适配器,并让它使用侦听器处理所有必要的更改

还有另一种解决方案,不需要修改ViewPager和FragmentStatePagerAdapter的源代码,它与作者使用的FragmentPagerAdapter基类一起工作。

我想先回答作者的问题,他应该使用哪个ID;它是容器的ID,也就是视图分页器本身的ID。但是,正如您自己可能注意到的那样,在代码中使用该ID不会发生任何事情。我将解释为什么:

首先,要使ViewPager重新填充页面,需要调用驻留在适配器基类中的notifyDataSetChanged()。

其次,ViewPager使用getItemPosition()抽象方法来检查哪些页面应该销毁,哪些页面应该保留。这个函数的默认实现总是返回POSITION_UNCHANGED,这将导致ViewPager保留所有当前页面,从而不会附加新页面。因此,为了使片段替换工作,getItemPosition()需要在适配器中被重写,并且在使用旧的、要隐藏的片段作为参数调用时必须返回POSITION_NONE。

这也意味着你的适配器总是需要知道哪个片段应该显示在位置0,FirstPageFragment还是NextFragment。一种方法是在创建FirstPageFragment时提供一个侦听器,当切换片段时将调用该侦听器。我认为这是一件好事,让你的片段适配器处理所有的片段切换和调用ViewPager和FragmentManager。

第三,FragmentPagerAdapter通过从位置派生的名称来缓存使用的片段,因此,如果在位置0有一个片段,即使类是新的,它也不会被替换。有两种解决方案,但最简单的是使用FragmentTransaction的remove()函数,该函数也将删除其标记。

这是大量的文本,这里是代码,应该在你的情况下工作:

public class MyAdapter extends FragmentPagerAdapter
{
    static final int NUM_ITEMS = 2;
    private final FragmentManager mFragmentManager;
    private Fragment mFragmentAtPos0;

    public MyAdapter(FragmentManager fm)
    {
        super(fm);
        mFragmentManager = fm;
    }

    @Override
    public Fragment getItem(int position)
    {
        if (position == 0)
        {
            if (mFragmentAtPos0 == null)
            {
                mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()
                {
                    public void onSwitchToNextFragment()
                    {
                        mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();
                        mFragmentAtPos0 = NextFragment.newInstance();
                        notifyDataSetChanged();
                    }
                });
            }
            return mFragmentAtPos0;
        }
        else
            return SecondPageFragment.newInstance();
    }

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

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

public interface FirstPageFragmentListener
{
    void onSwitchToNextFragment();
}

我按照@wize和@mdelolmo的答案,我得到了解决方案。由于吨。但是,我稍微调整了这些解决方案,以提高内存消耗。

我观察到的问题:

它们保存被替换的Fragment实例。在我的情况下,它是一个持有MapView的片段,我认为它的成本很高。所以,我维护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处使用了两个片段TabReplaceFragment和DemoTab2Fragment。在所有其他情况下,我使用DemoTabFragment实例。

解释:

我将Switch从Activity传递到DemoCollectionPagerAdapter。基于这个开关的状态,我们将显示正确的片段。当开关检查被更改时,我调用SwitchFragListener的onSwitchToNextFragment方法,其中我将pagerAdapterPosChanged变量的值更改为POSITION_NONE。查看更多关于POSITION_NONE的信息。这将使getItem无效,我有逻辑实例化正确的片段在那里。抱歉,如果解释得有点乱的话。

再次感谢@wize和@mdelolmo的原创想法。

希望这对你有帮助。:)

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