【问题标题】:Replace Fragment inside a ViewPager替换 ViewPager 中的 Fragment
【发布时间】:2011-12-05 03:43:44
【问题描述】:

我正在尝试使用带有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 会显示奇怪的行为,如下所示。

一开始的情况是

更换后的情况是

如您所见,有问题,我希望在替换片段后找到与第一张图片相同的状态。

【问题讨论】:

标签: android view android-fragments android-viewpager pager


【解决方案1】:

还有一种解决方案,不需要修改ViewPagerFragmentStatePagerAdapter的源码,与作者使用的FragmentPagerAdapter基类配合使用。

我想首先回答作者关于他应该使用哪个 ID 的问题;它是容器的 ID,即视图寻呼机本身的 ID。但是,您可能已经注意到,在代码中使用该 ID 不会导致任何事情发生。我会解释原因:

首先,要让ViewPager 重新填充页面,您需要调用位于适配器基类中的notifyDataSetChanged()

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

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

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

那是很多文字,这里的代码应该适用于你的情况:

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

希望这对任何人都有帮助!

【讨论】:

  • 也适用于我,但是如何实现后退按钮以显示第一个片段?
  • 很好的解释,但您能否发布完整的源代码,特别是 FirstPageFragment 和 SecondPageFragment 类。
  • 如何在 newInstance() 中将 FirstPageFragmentListener 添加到 Bundle 中?
  • 你能在你处理FirstPageFragment.newInstance()监听器参数的地方添加代码吗?
  • 这仅适用于我在删除片段时调用 commitNow() 而不是 commit() 的情况。不知道为什么我的用例有所不同,但希望这可以节省一些时间。
【解决方案2】:

截至 2012 年 11 月 13 日,在 ViewPager 中重新调整片段似乎变得容易多了。 Google 发布了支持嵌套片段的 Android 4.2,新的 Android Support Library v11 也支持它,所以这将一直工作到 1.6

除了使用 getChildFragmentManager 之外,它与替换片段的常规方法非常相似。当用户单击后退按钮时,除了nested fragment backstack isn't popped 之外,它似乎工作。根据该链接问题中的解决方案,您需要在片段的子管理器上手动调用 popBackStackImmediate() 。因此,您需要覆盖 ViewPager 活动的 onBackPressed(),您将在其中获取 ViewPager 的当前片段并对其调用 getChildFragmentManager().popBackStackImmediate()。

获取当前显示的 Fragment 也有点麻烦,我使用了这个 dirty "android:switcher:VIEWPAGER_ID:INDEX" solution,但您也可以自己跟踪 ViewPager 的所有片段,如第二个解决方案 on this page 中所述。

所以这是我的 ViewPager 代码,它有 4 个 ListViews,当用户单击一行时,ViewPager 中会显示一个详细视图,并且后退按钮工作。为简洁起见,我尝试仅包含相关代码,如果您希望将完整的应用程序上传到 GitHub,请留下评论。

HomeActivity.java

 public class HomeActivity extends SherlockFragmentActivity {
FragmentAdapter mAdapter;
ViewPager mPager;
TabPageIndicator mIndicator;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    mAdapter = new FragmentAdapter(getSupportFragmentManager());
    mPager = (ViewPager)findViewById(R.id.pager);
    mPager.setAdapter(mAdapter);
    mIndicator = (TabPageIndicator)findViewById(R.id.indicator);
    mIndicator.setViewPager(mPager);
}

// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.
@Override
public void onBackPressed() {
    Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());
    if (fragment != null) // could be null if not instantiated yet
    {
        if (fragment.getView() != null) {
            // Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.
            if (!fragment.getChildFragmentManager().popBackStackImmediate()) {
                finish();
            }
        }
    }
}

class FragmentAdapter extends FragmentPagerAdapter {        
    public FragmentAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
        case 0:
            return ListProductsFragment.newInstance();
        case 1:
            return ListActiveSubstancesFragment.newInstance();
        case 2:
            return ListProductFunctionsFragment.newInstance();
        case 3:
            return ListCropsFragment.newInstance();
        default:
            return null;
        }
    }

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

 }
}

ListProductsFragment.java

public class ListProductsFragment extends SherlockFragment {
private ListView list;

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View V = inflater.inflate(R.layout.list, container, false);
    list = (ListView)V.findViewById(android.R.id.list);
    list.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> parent, View view,
            int position, long id) {
          // This is important bit
          Fragment productDetailFragment = FragmentProductDetail.newInstance();
          FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
          transaction.addToBackStack(null);
          transaction.replace(R.id.products_list_linear, productDetailFragment).commit();
        }
      });       
    return V;
}
}

【讨论】:

  • 您可以将代码上传到 github 吗?谢谢。
  • @georgiecasey:我下载了 API 17,Android 4.2,它允许我开始使用 getChildFragmentManager()。但我必须从您的解决方案中遗漏一些东西,因为我现在在屏幕上重叠旧 + 新片段。注意:我正在尝试将您的解决方案与来自developer.android.com/reference/android/support/v4/view/… 的 Google 示例 TabsAdapter 代码结合使用。感谢您提供任何建议和/或参考代码。
  • R.id.products_list_linear 指的是什么?请按照提供的方式链接到您的代码。
  • @howettl 引用外部片段成员的 ID。解决这个问题的关键是使用嵌套片段(这样你就不必操作 viewpager 适配器)。
  • 这个有没有到 github 上?
【解决方案3】:

基于@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.pageListener.onSwitchToNextFragment()NextFragment.pageListener.onSwitchToNextFragment()

【讨论】:

  • 如何进行替换?如果不覆盖 onSwitchToNext 方法,就无法初始化监听器。
  • 如果你旋转设备,你不会丢失保存在 mFragmentAtPos0 中的状态吗?
  • @seb,我实际上改进了这个解决方案,以便在保存活动状态时保存mFragmentAtPos0 引用。这不是最优雅的解决方案,但很有效。
  • 你可以看到my answer。它替换片段并返回到第一个。
  • @mdelolmo 你能看看这个关于 ViewPager 的问题吗?谢谢stackoverflow.com/questions/27937250/…
【解决方案4】:

我已经实现了一个解决方案:

  • 标签内的动态片段替换
  • 维护每个选项卡的历史记录
  • 处理方向变化

实现这一点的技巧如下:

  • 使用 notifyDataSetChanged() 方法应用片段替换
  • 片段管理器仅用于后台,不用于片段替换
  • 使用备忘录模式(堆栈)维护历史记录

适配器代码如下:

public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.TabListener, ViewPager.OnPageChangeListener {

/** The sherlock fragment activity. */
private final SherlockFragmentActivity mActivity;

/** The action bar. */
private final ActionBar mActionBar;

/** The pager. */
private final ViewPager mPager;

/** The tabs. */
private List<TabInfo> mTabs = new LinkedList<TabInfo>();

/** The total number of tabs. */
private int TOTAL_TABS;

private Map<Integer, Stack<TabInfo>> history = new HashMap<Integer, Stack<TabInfo>>();

/**
 * Creates a new instance.
 *
 * @param activity the activity
 * @param pager    the pager
 */
public TabsAdapter(SherlockFragmentActivity activity, ViewPager pager) {
    super(activity.getSupportFragmentManager());
    activity.getSupportFragmentManager();
    this.mActivity = activity;
    this.mActionBar = activity.getSupportActionBar();
    this.mPager = pager;
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
}

/**
 * Adds the tab.
 *
 * @param image         the image
 * @param fragmentClass the class
 * @param args          the arguments
 */
public void addTab(final Drawable image, final Class fragmentClass, final Bundle args) {
    final TabInfo tabInfo = new TabInfo(fragmentClass, args);
    final ActionBar.Tab tab = mActionBar.newTab();
    tab.setTabListener(this);
    tab.setTag(tabInfo);
    tab.setIcon(image);

    mTabs.add(tabInfo);
    mActionBar.addTab(tab);

    notifyDataSetChanged();
}

@Override
public Fragment getItem(final int position) {
    final TabInfo tabInfo = mTabs.get(position);
    return Fragment.instantiate(mActivity, tabInfo.fragmentClass.getName(), tabInfo.args);
}

@Override
public int getItemPosition(final Object object) {
    /* Get the current position. */
    int position = mActionBar.getSelectedTab().getPosition();

    /* The default value. */
    int pos = POSITION_NONE;
    if (history.get(position).isEmpty()) {
        return POSITION_NONE;
    }

    /* Checks if the object exists in current history. */
    for (Stack<TabInfo> stack : history.values()) {
        TabInfo c = stack.peek();
        if (c.fragmentClass.getName().equals(object.getClass().getName())) {
            pos = POSITION_UNCHANGED;
            break;
        }
    }
    return pos;
}

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

@Override
public void onPageScrollStateChanged(int arg0) {
}

@Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}

@Override
public void onPageSelected(int position) {
    mActionBar.setSelectedNavigationItem(position);
}

@Override
public void onTabSelected(final ActionBar.Tab tab, final FragmentTransaction ft) {
    TabInfo tabInfo = (TabInfo) tab.getTag();
    for (int i = 0; i < mTabs.size(); i++) {
        if (mTabs.get(i).equals(tabInfo)) {
            mPager.setCurrentItem(i);
        }
    }
}

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}

public void replace(final int position, final Class fragmentClass, final Bundle args) {
    /* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();
}

/**
 * Updates the tabs.
 *
 * @param tabInfo
 *          the new tab info
 * @param position
 *          the position
 */
private void updateTabs(final TabInfo tabInfo, final int position) {
    mTabs.remove(position);
    mTabs.add(position, tabInfo);
    mActionBar.getTabAt(position).setTag(tabInfo);
}

/**
 * Creates the history using the current state.
 */
public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

/**
 * Called on back
 */
public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

/**
 * Returns if the history is empty.
 *
 * @param position
 *          the position
 * @return  the flag if empty
 */
private boolean historyIsEmpty(final int position) {
    return history == null || history.isEmpty() || history.get(position).isEmpty();
}

private boolean isLastItemInHistory(final int position) {
    return history.get(position).size() == 1;
}

/**
 * Returns the previous state by the position provided.
 *
 * @param position
 *          the position
 * @return  the tab info
 */
private TabInfo getPrevious(final int position) {
    TabInfo currentTabInfo = history.get(position).pop();
    if (!history.get(position).isEmpty()) {
        currentTabInfo = history.get(position).peek();
    }
    return currentTabInfo;
}

/** The tab info class */
private static class TabInfo {

    /** The fragment class. */
    public Class fragmentClass;

    /** The args.*/
    public Bundle args;

    /**
     * Creates a new instance.
     *
     * @param fragmentClass
     *          the fragment class
     * @param args
     *          the args
     */
    public TabInfo(Class fragmentClass, Bundle args) {
        this.fragmentClass = fragmentClass;
        this.args = args;
    }

    @Override
    public boolean equals(final Object o) {
        return this.fragmentClass.getName().equals(o.getClass().getName());
    }

    @Override
    public int hashCode() {
        return fragmentClass.getName() != null ? fragmentClass.getName().hashCode() : 0;
    }

    @Override
    public String toString() {
        return "TabInfo{" +
                "fragmentClass=" + fragmentClass +
                '}';
    }
}

第一次添加所有选项卡时,我们需要调用方法 createHistory() 来创建初始历史记录

public void createHistory() {
    int position = 0;
    TOTAL_TABS = mTabs.size();
    for (TabInfo mTab : mTabs) {
        if (history.get(position) == null) {
            history.put(position, new Stack<TabInfo>());
        }
        history.get(position).push(new TabInfo(mTab.fragmentClass, mTab.args));
        position++;
    }
}

每次您想将片段替换为您调用的特定选项卡时: replace(final int position, final Class fragmentClass, final Bundle args)

/* Save the fragment to the history. */
    mActivity.getSupportFragmentManager().beginTransaction().addToBackStack(null).commit();

    /* Update the tabs. */
    updateTabs(new TabInfo(fragmentClass, args), position);

    /* Updates the history. */
    history.get(position).push(new TabInfo(mTabs.get(position).fragmentClass, mTabs.get(position).args));

    notifyDataSetChanged();

在后按时你需要调用 back() 方法:

public void back() {
    int position = mActionBar.getSelectedTab().getPosition();
    if (!historyIsEmpty(position)) {
        /* In case there is not any other item in the history, then finalize the activity. */
        if (isLastItemInHistory(position)) {
            mActivity.finish();
        }
        final TabInfo currentTabInfo = getPrevious(position);
        mTabs.clear();
        for (int i = 0; i < TOTAL_TABS; i++) {
            if (i == position) {
                mTabs.add(new TabInfo(currentTabInfo.fragmentClass, currentTabInfo.args));
            } else {
                TabInfo otherTabInfo = history.get(i).peek();
                mTabs.add(new TabInfo(otherTabInfo.fragmentClass, otherTabInfo.args));
            }
        }
    }
    mActionBar.selectTab(mActionBar.getTabAt(position));
    notifyDataSetChanged();
}

该解决方案适用于夏洛克操作栏和滑动手势。

【讨论】:

  • 太棒了!可以放到github上吗?
  • 这个实现是这里提供的所有实现中最干净的,并且解决了每一个问题。它应该是公认的答案。
  • 有人对此实现有疑问吗?我有2个。 1. 第一个背面没有注册 - 我必须点击两次才能注册。 2.在标签a上单击返回(两次)并转到标签b后,当我返回标签a时,它只显示标签b的内容
【解决方案5】:

tl;dr:使用负责替换其托管内容并跟踪后退导航历史记录的主机片段(例如在浏览器中)。

由于您的用例包含固定数量的选项卡,因此我的解决方案效果很好:想法是用自定义类 HostFragment 的实例填充 ViewPager,它能够替换其托管内容并保持自己的后退导航历史。要替换托管片段,您需要调用 hostfragment.replaceFragment() 方法:

public void replaceFragment(Fragment fragment, boolean addToBackstack) {
    if (addToBackstack) {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).addToBackStack(null).commit();
    } else {
        getChildFragmentManager().beginTransaction().replace(R.id.hosted_fragment, fragment).commit();
    }
}

该方法所做的只是将 ID 为 R.id.hosted_fragment 的框架布局替换为提供给该方法的片段。

查看我的tutorial on this topic 了解更多详情和 GitHub 上的完整工作示例!

【讨论】:

  • 虽然这看起来比较笨拙,但我认为实际上是最好的(就结构和易于维护而言)方法。
  • 我最终撞到了一个 NPE。 :/ 找不到要替换的View
【解决方案6】:

一些提出的解决方案在部分解决问题方面帮助了我很多,但解决方案中仍然缺少一件重要的事情,在某些情况下会产生意外异常和黑页内容而不是片段内容。

问题是 FragmentPagerAdapter 类正在使用项目 ID 将缓存的片段存储到 FragmentManager。出于这个原因,您还需要重写 getItemId(int position) 方法,以便它返回 e。 G。 position 用于顶级页面,100 + position 用于详细信息页面。否则会从缓存中返回之前创建的顶级片段,而不是细节级片段。

此外,我在这里分享一个完整的示例,如何使用 ViewPager 实现带有 Fragment 页面的类似标签的活动,并使用 RadioGroup 实现标签按钮> 允许用详细页面替换顶级页面,还支持后退按钮。此实现仅支持一层反向堆叠(项目列表 - 项目详细信息),但多级反向堆叠实现很简单。这个例子在正常情况下工作得很好,除了当你切换到 e 时它会抛出一个 NullPointerException。 G。第二页,更改第一页的片段(虽然不可见)并返回到第一页。一旦我弄清楚了,我会发布这个问题的解决方案:

public class TabsActivity extends FragmentActivity {

  public static final int PAGE_COUNT = 3;
  public static final int FIRST_PAGE = 0;
  public static final int SECOND_PAGE = 1;
  public static final int THIRD_PAGE = 2;

  /**
   * Opens a new inferior page at specified tab position and adds the current page into back
   * stack.
   */
  public void startPage(int position, Fragment content) {
    // Replace page adapter fragment at position.
    mPagerAdapter.start(position, content);
  }

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

    // Initialize basic layout.
    this.setContentView(R.layout.tabs_activity);

    // Add tab fragments to view pager.
    {
      // Create fragments adapter.
      mPagerAdapter = new PagerAdapter(pager);
      ViewPager pager = (ViewPager) super.findViewById(R.id.tabs_view_pager);
      pager.setAdapter(mPagerAdapter);

      // Update active tab in tab bar when page changes.
      pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int index, float value, int nextIndex) {
          // Not used.
        }

        @Override
        public void onPageSelected(int index) {
          RadioGroup tabs_radio_group = (RadioGroup) TabsActivity.this.findViewById(
            R.id.tabs_radio_group);
          switch (index) {
            case 0: {
              tabs_radio_group.check(R.id.first_radio_button);
            }
            break;
            case 1: {
              tabs_radio_group.check(R.id.second_radio_button);
            }
            break;
            case 2: {
              tabs_radio_group.check(R.id.third_radio_button);
            }
            break;
          }
        }

        @Override
        public void onPageScrollStateChanged(int index) {
          // Not used.
        }
      });
    }

    // Set "tabs" radio group on checked change listener that changes the displayed page.
    RadioGroup radio_group = (RadioGroup) this.findViewById(R.id.tabs_radio_group);
    radio_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
      @Override
      public void onCheckedChanged(RadioGroup radioGroup, int id) {
        // Get view pager representing tabs.
        ViewPager view_pager = (ViewPager) TabsActivity.this.findViewById(R.id.tabs_view_pager);
        if (view_pager == null) {
          return;
        }

        // Change the active page.
        switch (id) {
          case R.id.first_radio_button: {
            view_pager.setCurrentItem(FIRST_PAGE);
          }
          break;
          case R.id.second_radio_button: {
            view_pager.setCurrentItem(SECOND_PAGE);
          }
          break;
          case R.id.third_radio_button: {
            view_pager.setCurrentItem(THIRD_PAGE);
          }
          break;
        }
      });
    }
  }

  @Override
  public void onBackPressed() {
    if (!mPagerAdapter.back()) {
      super.onBackPressed();
    }
  }

  /**
   * Serves the fragments when paging.
   */
  private class PagerAdapter extends FragmentPagerAdapter {

    public PagerAdapter(ViewPager container) {
      super(TabsActivity.this.getSupportFragmentManager());

      mContainer = container;
      mFragmentManager = TabsActivity.this.getSupportFragmentManager();

      // Prepare "empty" list of fragments.
      mFragments = new ArrayList<Fragment>(){};
      mBackFragments = new ArrayList<Fragment>(){};
      for (int i = 0; i < PAGE_COUNT; i++) {
        mFragments.add(null);
        mBackFragments.add(null);
      }
    }

    /**
     * Replaces the view pager fragment at specified position.
     */
    public void replace(int position, Fragment fragment) {
      // Get currently active fragment.
      Fragment old_fragment = mFragments.get(position);
      if (old_fragment == null) {
        return;
      }

      // Replace the fragment using transaction and in underlaying array list.
      // NOTE .addToBackStack(null) doesn't work
      this.startUpdate(mContainer);
      mFragmentManager.beginTransaction().setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
        .remove(old_fragment).add(mContainer.getId(), fragment)
        .commit();
      mFragments.set(position, fragment);
      this.notifyDataSetChanged();
      this.finishUpdate(mContainer);
    }

    /**
     * Replaces the fragment at specified position and stores the current fragment to back stack
     * so it can be restored by #back().
     */
    public void start(int position, Fragment fragment) {
      // Remember current fragment.
      mBackFragments.set(position, mFragments.get(position));

      // Replace the displayed fragment.
      this.replace(position, fragment);
    }

    /**
     * Replaces the current fragment by fragment stored in back stack. Does nothing and returns
     * false if no fragment is back-stacked.
     */
    public boolean back() {
      int position = mContainer.getCurrentItem();
      Fragment fragment = mBackFragments.get(position);
      if (fragment == null) {
        // Nothing to go back.
        return false;
      }

      // Restore the remembered fragment and remove it from back fragments.
      this.replace(position, fragment);
      mBackFragments.set(position, null);
      return true;
    }

    /**
     * Returns fragment of a page at specified position.
     */
    @Override
    public Fragment getItem(int position) {
      // If fragment not yet initialized, create its instance.
      if (mFragments.get(position) == null) {
        switch (position) {
          case FIRST_PAGE: {
            mFragments.set(FIRST_PAGE, new DefaultFirstFragment());
          }
          break;
          case SECOND_PAGE: {
            mFragments.set(SECOND_PAGE, new DefaultSecondFragment());
          }
          break;
          case THIRD_PAGE: {
            mFragments.set(THIRD_PAGE, new DefaultThirdFragment());
          }
          break;
        }
      }

      // Return fragment instance at requested position.
      return mFragments.get(position);
    }

    /**
     * Custom item ID resolution. Needed for proper page fragment caching.
     * @see FragmentPagerAdapter#getItemId(int).
     */
    @Override
    public long getItemId(int position) {
      // Fragments from second level page hierarchy have their ID raised above 100. This is
      // important to FragmentPagerAdapter because it is caching fragments to FragmentManager with
      // this item ID key.
      Fragment item = mFragments.get(position);
      if (item != null) {
        if ((item instanceof NewFirstFragment) || (item instanceof NewSecondFragment) ||
          (item instanceof NewThirdFragment)) {
          return 100 + position;
        }
      }

      return position;
    }

    /**
     * Returns number of pages.
     */
    @Override
    public int getCount() {
      return mFragments.size();
    }

    @Override
    public int getItemPosition(Object object)
    {
      int position = POSITION_UNCHANGED;
      if ((object instanceof DefaultFirstFragment) || (object instanceof NewFirstFragment)) {
        if (object.getClass() != mFragments.get(FIRST_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultSecondragment) || (object instanceof NewSecondFragment)) {
        if (object.getClass() != mFragments.get(SECOND_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      if ((object instanceof DefaultThirdFragment) || (object instanceof NewThirdFragment)) {
        if (object.getClass() != mFragments.get(THIRD_PAGE).getClass()) {
          position = POSITION_NONE;
        }
      }
      return position;
    }

    private ViewPager mContainer;
    private FragmentManager mFragmentManager;

    /**
     * List of page fragments.
     */
    private List<Fragment> mFragments;

    /**
     * List of page fragments to return to in onBack();
     */
    private List<Fragment> mBackFragments;
  }

  /**
   * Tab fragments adapter.
   */
  private PagerAdapter mPagerAdapter;
}

【讨论】:

  • 嗯,这段代码还有另一个问题。当我想从已删除并再次添加的片段中启动活动时,我收到错误消息:“失败保存状态:活动 NewFirstFragment{405478a0} 已清除索引:-1”(完整堆栈跟踪:nopaste.info/547a23dfea.html)。查看android资源,我发现它与backstack有关,但到目前为止我不知道如何修复它。有人知道吗?
  • 酷,使用 mFragmentManager.beginTransaction().detach(old_fragment).attach(fragment).commit(); 而不是
    mFragmentManager.beginTransaction().setTransition (FragmentTransaction.TRANSIT_FRAGMENT_OPEN).remove(old_fragment).add(mContainer.getId(), fragment).commit();
    在上面的例子中似乎解决了这两个问题:-)。
  • 如果您使用此方法,请确保在旋转设备时不会让 Android 重新创建您的 Activity。否则,PagerAdapter 将被重新创建,您将丢失 mBackFragments。这也会严重混淆 FragmentManager。使用 BackStack 可以避免这个问题,但代价是更复杂的 PagerAdapter 实现(即您不能从 FragmentPagerAdapter 继承。)
【解决方案7】:

我已经为索引 2 和 3 创建了一个包含 3 个元素和 2 个子元素的 ViewPager,这就是我想做的事情..

我在 StackOverFlow 以前的问题和答案的帮助下实现了这一点,这里是链接。

ViewPagerChildFragments

【讨论】:

  • 如果您旋转设备,您的项目将失败。
【解决方案8】:

要替换ViewPager 中的片段,您可以将ViewPagerPagerAdapterFragmentStatePagerAdapter 类的源代码移动到您的项目中并添加以下代码。

转入ViewPager

public void notifyItemChanged(Object oldItem, Object newItem) {
    if (mItems != null) {
            for (ItemInfo itemInfo : mItems) {
                        if (itemInfo.object.equals(oldItem)) {
                                itemInfo.object = newItem;
                        }
                    }
       }
       invalidate();
    }

进入 FragmentStatePagerAdapter:

public void replaceFragmetns(ViewPager container, Fragment oldFragment, Fragment newFragment) {
       startUpdate(container);

       // remove old fragment

       if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
       int position = getFragmentPosition(oldFragment);
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
        }
        mSavedState.set(position, null);
        mFragments.set(position, null);

        mCurTransaction.remove(oldFragment);

        // add new fragment

        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        mFragments.set(position, newFragment);
        mCurTransaction.add(container.getId(), newFragment);

       finishUpdate(container);

       // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
       handleGetItemInbalidated(container, oldFragment, newFragment);

       container.notifyItemChanged(oldFragment, newFragment);
    }

protected abstract void handleGetItemInbalidated(View container, Fragment oldFragment, Fragment newFragment);
protected abstract int  getFragmentPosition(Fragment fragment);

handleGetItemInvalidated() 确保在下一次调用 getItem() 之后返回 newFragment getFragmentPosition() 返回片段在适配器中的位置。

现在,替换片段调用

mAdapter.replaceFragmetns(mViewPager, oldFragment, newFragment);

如果您对示例项目感兴趣,请向我索取资源。

【讨论】:

  • getFragmentPosition() 和 handleGetItemInbalidated() 方法应该如何?我无法更新 getItem() 以返回 NewFragment.. 请帮助..
  • 嗨!请在此链接上找到测试项目files.mail.ru/eng?back=%2FEZU6G4
  • 如果您旋转设备,您的测试项目将失败。
  • @AndroidTeamAtMail.Ru 链接似乎已过期。请您分享代码
  • 你可以看到my answer。它替换片段并返回到第一个。
【解决方案9】:

与 AndroidTeam 的解决方案配合得很好,但是我发现我需要能够像 FrgmentTransaction.addToBackStack(null) 一样返回,但仅仅添加它只会导致 Fragment 被替换而不通知 ViewPager。将提供的解决方案与这个小增强功能相结合,您只需覆盖活动的onBackPressed() 方法即可返回到以前的状态。最大的缺点是一次只能返回一个,可能会导致多次返回点击

private ArrayList<Fragment> bFragments = new ArrayList<Fragment>();
private ArrayList<Integer> bPosition = new ArrayList<Integer>();

public void replaceFragmentsWithBackOut(ViewPager container, Fragment oldFragment, Fragment newFragment) {
    startUpdate(container);

    // remove old fragment

    if (mCurTransaction == null) {
         mCurTransaction = mFragmentManager.beginTransaction();
     }
    int position = getFragmentPosition(oldFragment);
     while (mSavedState.size() <= position) {
         mSavedState.add(null);
     }

     //Add Fragment to Back List
     bFragments.add(oldFragment);

     //Add Pager Position to Back List
     bPosition.add(position);

     mSavedState.set(position, null);
     mFragments.set(position, null);

     mCurTransaction.remove(oldFragment);

     // add new fragment

     while (mFragments.size() <= position) {
         mFragments.add(null);
     }
     mFragments.set(position, newFragment);
     mCurTransaction.add(container.getId(), newFragment);

    finishUpdate(container);

    // ensure getItem returns newFragemtn after calling handleGetItemInbalidated()
    handleGetItemInvalidated(container, oldFragment, newFragment);

    container.notifyItemChanged(oldFragment, newFragment);
 }


public boolean popBackImmediate(ViewPager container){
    int bFragSize = bFragments.size();
    int bPosSize = bPosition.size();

    if(bFragSize>0 && bPosSize>0){
        if(bFragSize==bPosSize){
            int last = bFragSize-1;
            int position = bPosition.get(last);

            //Returns Fragment Currently at this position
            Fragment replacedFragment = mFragments.get(position);               
            Fragment originalFragment = bFragments.get(last);

            this.replaceFragments(container, replacedFragment, originalFragment);

            bPosition.remove(last);
            bFragments.remove(last);

            return true;
        }
    }

    return false;       
}

希望这对某人有所帮助。

getFragmentPosition() 而言,它几乎与getItem() 相反。你知道哪些片段去哪里了,只要确保你返回它所在的正确位置。下面是一个例子:

    @Override
    protected int getFragmentPosition(Fragment fragment) {
            if(fragment.equals(originalFragment1)){
                return 0;
            }
            if(fragment.equals(replacementFragment1)){
                return 0;
            }
            if(fragment.equals(Fragment2)){
                return 1;
            }
        return -1;
    }

【讨论】:

    【解决方案10】:

    在您的 onCreateView 方法中,container 实际上是一个 ViewPager 实例。

    所以,只需调用

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

    将更改您的ViewPager 中的当前片段。

    【讨论】:

    • 经过数小时的搜索,正是这一行代码让我的所有选项卡都能正常工作。 vpViewPager.setCurrentItem(1);。我经历了每个人的例子,每次都没有发生任何事情,直到我终于找到你的例子。谢谢。
    • 惊呆了……这太棒了!!我没有意识到容器变量是 viewpager 本身。在所有其他冗长的答案之前,必须首先探索这个答案。另外,没有将监听器变量传递给片段构造函数,这简化了当配置更改时 Android 使用默认的无参数构造函数自动重新创建片段时的所有实现。
    • 这不只是更改为现有标签吗?这与替换片段有什么关系?
    • 同意@behelit,这并不能回答问题,它只会将ViewPager 移动到另一个页面,它不会替换任何片段。
    【解决方案11】:

    这是我对这个问题的相对简单的解决方案。此解决方案的关键是使用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);
        }
    }
    

    【讨论】:

      【解决方案12】:

      我还提出了一个解决方案,它与 Stacks 一起使用。这是一种更模块化的方法,因此您不必在FragmentPagerAdapter 中指定每个片段和详细片段。它建立在 ActionbarSherlock 的示例之上,如果我是来自 Google Demo App 的话。

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

      如果您想在片段状态被删除时保存片段状态。让你的 Fragment 实现接口 SaveStateBundle 在函数中返回一个带有你保存状态的包。通过this.getArguments()实例化后获取bundle。

      您可以像这样实例化标签:

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

      如果您想在选项卡堆栈顶部添加片段,则类似。 重要提示:我认为,如果你想在两个选项卡上拥有 2 个相同类的实例,它是行不通的。 我一起快速完成了这个解决方案,所以我只能分享它而不提供任何经验。

      【讨论】:

      • 按下后退按钮时出现空指针异常,并且我替换的片段也不可见。所以也许我做错了什么:正确更改片段的正确调用应该是什么?
      • 更新:我已经修复了大部分内容,但是,如果我在添加新片段后按下后退按钮,然后转到另一个选项卡并返回,我会得到一个空指针异常。所以我有 2 个问题: 1:如何删除选项卡的历史记录并确保只有最新的片段(堆栈顶部)仍然存在? 2:如何摆脱空指针异常?
      • @Jeroen 1:您可以从 mTab​​StackMap 中获取正确的堆栈,然后删除位于堆栈顶部下方的每个 TabInfo 对象,2:很难说,然后用 stacktrace 提问我可以尝试找出,你确定这不是你的代码失败吗?如果您切换到直接附近(右侧或左侧)的选项卡(然后返回),是否也会发生这种情况?
      • 好的,我遇到了一个新问题:如果我旋转屏幕,那么堆栈似乎又是空的了?它似乎失去了对以前项目的跟踪......
      • @seb 谢谢。我正在使用此代码,但现在可以使用嵌套片段,因此不再是问题。 :)
      【解决方案13】:

      在 viewpager 中替换 Fragment 非常复杂,但很有可能并且看起来非常漂亮。首先,您需要让 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 是一个“灵活的”片段。它可以采用你给它的任何片段类型。这使它成为您最好的朋友,将位置 0 的片段更改为您想要的片段。
      2. 请注意放置在 'newInstance(listener) 构造函数中的侦听器。这些是你将如何调用fragment0Changed(String newFragmentIdentification)`。以下代码显示了如何在片段内创建侦听器。

        静态 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 适配器,并让它使用侦听器处理所有必要的更改

      【讨论】:

      • 感谢您提供此解决方案。但是旋转屏幕后您是否有任何问题。当我旋转屏幕并尝试在同一选项卡中重新加载另一个片段时,我的应用程序崩溃了。
      • 自从我完成 Android 开发以来已经有一段时间了,但我的猜测是您的应用程序不再依赖某个数据。当您旋转时,整个活动会重新启动,如果您尝试在没有所需所有数据的情况下重新加载之前的状态(例如来自 onPause、onResume 中的捆绑包),可能会导致崩溃
      【解决方案14】:

      这是我实现这一目标的方法。

      首先在viewPager选项卡中添加Root_fragment,您要在其中实现按钮单击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>
      

      然后,在RootTabFragment onCreateView 内部,为您的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 时旋转屏幕时...
      【解决方案15】:

      我找到了简单的解决方案,即使您想在中间添加新片段或替换当前片段,它也能正常工作。在我的解决方案中,您应该覆盖 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()

      【讨论】:

        【解决方案16】:

        我做了一些类似于 wize 的事情,但在我的回答中,你可以随时在两个片段之间进行更改。有了 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);
        
        }
        

        【讨论】:

          【解决方案17】:

          我遵循 @wize@mdelolmo 的答案,我得到了解决方案。谢谢吨。但是,我稍微调整了这些解决方案以改善内存消耗。

          我观察到的问题:

          他们保存被替换的Fragment 的实例。就我而言,它是一个包含MapView 的片段,我认为它的成本很高。所以,我维护的是 FragmentPagerPositionChanged (POSITION_NONE or 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 的原创创意。

          希望这会有所帮助。 :)

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

          【讨论】:

            【解决方案18】:

            经过研究,我找到了短代码的解决方案。 首先在片段上创建一个公共实例,如果片段没有在方向改变时重新创建,则只需在 onSaveInstanceState 上删除您的片段。

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

            【讨论】:

              猜你喜欢
              • 2017-04-07
              • 1970-01-01
              • 2013-09-06
              • 2013-09-15
              • 1970-01-01
              • 2013-02-25
              • 2014-02-23
              • 1970-01-01
              • 2012-12-31
              相关资源
              最近更新 更多