替换ViewPager中的片段


268

我试图使用片段与ViewPager使用FragmentPagerAdapter。我要实现的目标是ViewPager用另一个替换位于第一页的片段。

寻呼机由两页组成。第一个是FirstPagerFragment,第二个是SecondPagerFragment。单击第一页的按钮。我想FirstPagerFragment用NextFragment代替。

下面是我的代码。

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 2015年

@Noodles:我需要这样做。您的问题产生了很多不同答案的冗长话题。到底什么最有效?非常感谢!
narb

1
@JeroenVannevel你可能已经想通了,但布局图是developer.android.com/tools/performance/hierarchy-viewer/...
ANKIT Popli

遇到一些问题需要您的帮助stackoverflow.com/questions/40149039/…–
albert

Answers:


162

有不需要的修改源代码的另一种解决方案ViewPagerFragmentStatePagerAdapter,并将其与作品FragmentPagerAdapter作者使用的基类。

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

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

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

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

第三,FragmentPagerAdapter使用从该位置派生的名称缓存使用的片段,因此,如果位置0处有一个片段,即使该类是新的,也不会被替换。有两种解决方案,但最简单的方法是使用的remove()功能FragmentTransaction,这也会删除其标签。

那是很多文本,下面是适合您的情况的代码:

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

希望这对任何人有帮助!


24
我也可以使用,但是如何实现后退按钮以显示第一个片段?
user1159819'7

6
很好的解释,但您可以发布完整的源代码,尤其是FirstPageFragment和SecondPageFragment类。
georgiecasey 2012年

6
如何在newInstance()中将FirstPageFragmentListener添加到Bundle中?
米格尔

4
您可以将代码添加到使用FirstPageFragment.newInstance()监听程序参数处理的位置吗?
MiguelHincapieC

6
仅当删除片段时我调用commitNow()而不是commit()时,这才对我有用。不知道为什么我的用例不同,但是希望这可以节省一些时间。
伊恩·洛夫乔伊

37

截至2012年11月13日,在ViewPager中重排片段似乎变得容易得多。Google发布了支持嵌套片段的Android 4.2,并且新的Android支持库v11也支持它,因此它将一直支持1.6

除了使用getChildFragmentManager之外,它与替换片段的常规方法非常相似。似乎可行,除了当用户单击“后退”按钮时未弹出嵌套片段后退栈时。根据该链接问题中的解决方案,您需要在片段的子管理器上手动调用popBackStackImmediate()。因此,您需要重写ViewPager活动的onBackPressed(),在该活动中您将获取ViewPager的当前片段,并对其调用getChildFragmentManager()。popBackStackImmediate()。

获得当前正在显示的片段是有点哈克为好,我用这个肮脏的“Android:切换:VIEWPAGER_ID:INDEX”的解决方案,但你也可以跟踪ViewPager自己的所有片段作为第二个解决方案解释此页面上

因此,这是我的ViewPager代码,该ViewPager具有4个ListView,当用户单击一行时,在后退按钮起作用的情况下,详细视图显示在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;
}
}

7
您可以将代码上传到github吗?谢谢。
Gautham

4
@georgiecasey:我下载了Android 4.2 API 17,该API允许我开始使用getChildFragmentManager()。但是我一定从您的解决方案中丢失了一些东西,因为我现在在屏幕上看到重叠的旧片段和新片段。注意:我正在尝试将您的解决方案与developer.android.com/reference/android/support/v4/view/…中的Google示例TabsAdapter代码配合使用。感谢您的任何建议和/或参考代码。
gcl1

27
R.id.products_list_linear指的是什么?请链接到您提供的代码。
howettl

1
@howettl ID引用外部片段成员。解决此问题的关键是使用嵌套的片段(这样就不必操纵Viewpager适配器)。
IndrekKõue2013年

2
这曾经到github吗?
巴鲁克2014年

32

基于@wize的回答,我发现它很有帮助且优雅,我可以部分实现我想要的功能,因为我希望功能一旦替换就可以返回第一个Fragment。我实现了一点点修改他的代码。

这将是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方法,则无法初始化监听器。
莱安德罗斯2012年

1
如果您旋转设备,您不会丢失保存在mFragmentAtPos0中的状态吗?
2012年

@seb,我实际上改进了此解决方案,以便在保存mFragmentAtPos0活动状态时保存参考。这不是最优雅的解决方案,但可以。
mdelolmo 2012年

你可以看到我的答案。它替换片段并返回到第一个片段。
2013年

@mdelolmo您能否看一下有关ViewPager的问题?谢谢stackoverflow.com/questions/27937250/...
安和武

21

我已经实现了以下解决方案:

  • 标签内的动态片段替换
  • 每个标签的历史记录维护
  • 适应方向变化

实现此目的的技巧如下:

  • 使用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 ClassfragmentClass,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上吗?
尼克拉姆斯2014年

此实现是此处提供的所有实现中最干净的,并且解决了每个问题。它应该是公认的答案。
dazedviper 2014年

有人对此实施有疑问吗?我有两个。1.第一背未注册-我必须单击两次以进行注册。2.单击选项卡a上的后退(两次)并转到选项卡b后,当我返回选项卡a时,它仅显示选项卡b的内容
yaronbic 2015年

7

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

由于您的用例包含固定数量的选项卡,因此我的解决方案效果很好:想法是用自定义类的实例填充ViewPager,该类HostFragment可以替换其托管内容并保留其自己的后退导航历史记录。要替换托管片段,您可以调用方法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();
    }
}

该方法所做的就是R.id.hosted_fragment用提供给该方法的片段用id替换框架布局。

查看我关于该主题的教程,以获取更多详细信息以及GitHub上的完整工作示例!


尽管看起来比较笨拙,但我认为实际上这是最好的方法(就结构和易于维护而言)。
Sira Lam

我最终与NPE发生冲突。:/找不到要替换的视图
Fariz Fakkel

6

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

事实是FragmentPagerAdapter类使用项ID将缓存的片段存储到FragmentManager。出于这个原因,您还需要重写getItemId(INT位置),以便它返回如法位置的顶级页,100 +位置的详细信息页面。否则,将从缓存中返回先前创建的顶级片段,而不是详细信息片段。

此外,我在这里分享了一个完整的示例,该示例说明如何使用ViewPagerRadioGroup来使用Fragment页面和Tab按钮来实现类似于Tab的活动,该功能允许用详细页面替换顶层页面,还支持后退按钮。此实现仅支持一个级别的堆栈(项目列表-项目详细信息),但是多级别的堆栈实现非常简单。此示例在正常情况下效果很好,除了在您切换至第二页,更改第一页的片段(不可见)并返回第一页的情况下抛出NullPointerException之外。确定后,我将发布此问题的解决方案:

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

嗯,这段代码还有另一个问题。当我要从已删除并重新添加的片段开始活动时,出现错误:“故障保存状态:active NewFirstFragment {405478a0}已清除索引:-1”(完整堆栈跟踪:nopaste.info/547a23dfea.html)。看看android的源代码,我发现它与backstack有关,但是到目前为止我还不知道如何解决。有人有线索吗?
Blackhex 2012年

2
很酷,使用<code> mFragmentManager.beginTransaction()。detach(old_fragment).attach(fragment).commit(); </ code>代替<pre> mFragmentManager.beginTransaction()。setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN).remove(上例中的old_fragment).add(mContainer.getId(),fragment).commit(); </ pre>似乎解决了这两个问题:-)。
Blackhex 2012年

1
如果您使用此方法,请确保在旋转设备时不要让Android重新创建“活动”。否则,将重新创建PagerAdapter,并且您将丢失mBackFragments。这也将严重混淆FragmentManager。使用BackStack可以避免此问题,但要以更复杂的PagerAdapter实现为代价(即您不能从FragmentPagerAdapter继承。)
Norman


6

要替换一个片段里面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 ..请帮助..

嗨!请在此链接文件
在Mail.Ru

1
如果您旋转设备,则测试项目将失败。
2012年

1
@ AndroidTeamAtMail.Ru链接似乎已过期。请您共享代码
2013年

你可以看到我的答案。它替换片段并返回到第一个片段。
2012年

4

与AndroidTeam的解决方案配合使用时效果很好,但是我发现我需要像以前那样返回的功能,FrgmentTransaction.addToBackStack(null) 但是仅添加此功能只会导致在不通知ViewPager的情况下替换Fragment。将提供的解决方案与此较小的增强功能相结合,将使您仅通过覆盖活动的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;
    }

3

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

所以,只要打电话

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

将改变您的当前片段ViewPager


1
经过数小时的搜索,我只需要一行代码即可使所有选项卡正常工作。 vpViewPager.setCurrentItem(1);。我经历了每个人的例子,直到我终于找到你的例子,每次都没有发生任何事情。谢谢。
布兰登

1
令人震惊...这真是太棒了!! 我没有意识到容器变量就是viewpager本身。首先要探索所有其他冗长的答案。此外,没有将侦听器变量传递到片段构造函数,当配置更改时,Android使用默认的no-arg构造函数自动重新创建片段,从而简化了所有实现。
Kevin Lee

3
这不只是更改为现有选项卡吗?这与替换片段有什么关系?
相信

2
同意@behelit,这不会回答问题,它只会将移到ViewPager另一个页面,不会替换任何片段。
Yoann Hercouet'5

2

这是我相对简单的解决方案。此解决方案的关键是使用FragmentStatePagerAdapter而不是,FragmentPagerAdapter因为前者将为您删除未使用的片段,而后者仍保留其实例。第二个是POSITION_NONE在getItem()中的使用。我使用了一个简单的列表来跟踪我的片段。我的要求是用新列表立即替换整个片段列表,但可以轻松修改以下内容以替换单个片段:

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

我还提出了一个与Stacks一起使用的解决方案。这是一种更加模块化的方法,因此您不必在中指定每个Fragment和Detail Fragment FragmentPagerAdapter。它建立在ActionbarSherlock的示例之上,该示例推导出我是否正确地来自Google演示应用程序。

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

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

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

如果您想在选项卡堆栈的顶部添加片段,则可以类似地工作。 重要提示:我想,如果您想在两个选项卡的顶部拥有2个相同类的实例,将无法正常工作。我很快就完成了这个解决方案,所以我只能分享它而没有提供任何经验。


按下后退按钮时出现nullpointer异常,并且替换后的片段也不可见。因此,也许我做错了什么:正确地更改片段的正确调用应该是什么?
耶隆(Jeroen)2012年

更新:我已经修复了大多数问题,但是,如果我在添加新片段后按下后退按钮,然后转到另一个选项卡并返回,则会出现nullpointer异常。所以我有2个问题:1:如何删除标签的历史记录并确保仅存在最新的片段(TE堆栈的顶部)?2:如何摆脱nullpointer异常?
耶罗恩(Jeroen)2012年

@Jeroen 1:您可以从mTabStackMap中获取正确的堆栈,然后删除位于堆栈顶部下方的每个TabInfo对象; 2:很难说,用stacktrace询问问题,然后我可以尝试找出答案,您确定这不是代码失败吗?如果u切换到直接邻域(向右或向左)中的选项卡(然后再返回),也会发生这种情况吗?
2012年

好的,我遇到了一个新问题:如果旋转屏幕,那么堆栈似乎又变空了吗?似乎不太了解以前的项目...
Jeroen 2012年

1
@seb谢谢。我正在使用此代码,但是现在可以使用嵌套片段,因此不再是问题。:)
Garcon 2012年

2

替换viewpager中的片段非常繁琐,但很有可能,而且看起来非常漂亮。首先,您需要让viewpager本身处理片段的删除和添加。发生的是,当您替换SearchFragment中的片段时,您的viewpager保留了其片段视图。因此,您最终将得到一个空白页,因为当您尝试替换SearchFragment时,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();

    }

现在没有最后一块,您仍然会得到空白页。有点la脚,但这是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中的捆绑包中重新加载),则整个活动将重新启动,并可能导致崩溃
user3331142,2016年

2

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

首先添加 Root_fragment内部viewPager要在其中实现按钮点击选项卡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 onCreateViewfragmentChange为您实施FirstPagerFragment

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

之后,实施 onClick为您的按钮内部事件FirstPagerFragment,然后再次进行片段更改。

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

希望这对您有所帮助。


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扩展了具有method的抽象类PageFragment getPage()


1

我做的事情类似于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);

}

1

我跟随答案@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在第2个位置。在所有其他情况下,我都使用DemoTabFragment实例。

说明:

我正在Switch从Activity 传递到DemoCollectionPagerAdapter。根据此开关的状态,我们将显示正确的片段。更改开关检查后,我将调用SwitchFragListeneronSwitchToNextFragment方法,将pagerAdapterPosChanged变量的值更改为POSITION_NONE。进一步了解POSITION_NONE。这将使getItem无效,并且我有逻辑在该处实例化正确的片段。抱歉,如果说明有点混乱。

再次非常感谢@wize和@mdelolmo的初衷。

希望这会有所帮助。:)

让我知道此实现是否有缺陷。这将对我的项目有很大帮助。


0

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

 @Override
public void onSaveInstanceState(Bundle outState) {
    if (null != mCalFragment) {
        FragmentTransaction bt = getChildFragmentManager().beginTransaction();
        bt.remove(mFragment);
        bt.commit();
    }
    super.onSaveInstanceState(outState);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.